From 08d9dc5c6890d0483588d3a3d46d72bfa87be2e5 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 6 Aug 2018 10:29:37 -0700 Subject: [PATCH 001/362] Updated protocol version and versioning blurb --- node/Packet.hpp | 52 ++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/node/Packet.hpp b/node/Packet.hpp index 8e82bd348..828f549a8 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -45,31 +45,35 @@ /** * Protocol version -- incremented only for major changes * - * 1 - 0.2.0 ... 0.2.5 - * 2 - 0.3.0 ... 0.4.5 - * + Added signature and originating peer to multicast frame - * + Double size of multicast frame bloom filter - * 3 - 0.5.0 ... 0.6.0 - * + Yet another multicast redesign - * + New crypto completely changes key agreement cipher - * 4 - 0.6.0 ... 1.0.6 - * + BREAKING CHANGE: New identity format based on hashcash design - * 5 - 1.1.0 ... 1.1.5 - * + Supports echo - * + Supports in-band world (root server definition) updates - * + Clustering! (Though this will work with protocol v4 clients.) - * + Otherwise backward compatible with protocol v4 - * 6 - 1.1.5 ... 1.1.10 - * + Network configuration format revisions including binary values - * 7 - 1.1.10 ... 1.1.17 - * + Introduce trusted paths for local SDN use - * 8 - 1.1.17 ... 1.2.0 - * + Multipart network configurations for large network configs - * + Tags and Capabilities - * + Inline push of CertificateOfMembership deprecated - * 9 - 1.2.0 ... CURRENT + * 1 - 0.2.0 ... 0.2.5 + * 2 - 0.3.0 ... 0.4.5 + * + Added signature and originating peer to multicast frame + * + Double size of multicast frame bloom filter + * 3 - 0.5.0 ... 0.6.0 + * + Yet another multicast redesign + * + New crypto completely changes key agreement cipher + * 4 - 0.6.0 ... 1.0.6 + * + BREAKING CHANGE: New identity format based on hashcash design + * 5 - 1.1.0 ... 1.1.5 + * + Supports echo + * + Supports in-band world (root server definition) updates + * + Clustering! (Though this will work with protocol v4 clients.) + * + Otherwise backward compatible with protocol v4 + * 6 - 1.1.5 ... 1.1.10 + * + Network configuration format revisions including binary values + * 7 - 1.1.10 ... 1.1.17 + * + Introduce trusted paths for local SDN use + * 8 - 1.1.17 ... 1.2.0 + * + Multipart network configurations for large network configs + * + Tags and Capabilities + * + Inline push of CertificateOfMembership deprecated + * 9 - 1.2.0 ... 1.4.0 + * + Trace for remote debugging or diagnostics + * 10 - 1.4.0 ... CURRENT + * + Multipath support + * + Measurement of QoS metrics */ -#define ZT_PROTO_VERSION 9 +#define ZT_PROTO_VERSION 10 /** * Minimum supported protocol version From 20a25a6a45fcb37b9508855cfa870f2a7c09790a Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 6 Aug 2018 14:31:12 -0700 Subject: [PATCH 002/362] Added debug traces --- node/Path.hpp | 10 ++++++++++ node/Peer.cpp | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/node/Path.hpp b/node/Path.hpp index cafff8cf3..c861114ba 100644 --- a/node/Path.hpp +++ b/node/Path.hpp @@ -44,6 +44,8 @@ #include "../osdep/Phy.hpp" +#include "../include/ZeroTierDebug.h" + /** * Maximum return value of preferenceRank() */ @@ -314,6 +316,7 @@ public: */ inline void recordOutgoingPacket(int64_t now, int64_t packetId, uint16_t payloadLength, Packet::Verb verb) { + DEBUG_INFO(""); Mutex::Lock _l(_statistics_m); if (verb != Packet::VERB_ACK && verb != Packet::VERB_QOS_MEASUREMENT) { if ((packetId & (ZT_PATH_QOS_ACK_PROTOCOL_DIVISOR - 1)) == 0) { @@ -337,6 +340,7 @@ public: */ inline void recordIncomingPacket(int64_t now, int64_t packetId, uint16_t payloadLength, Packet::Verb verb) { + DEBUG_INFO(""); Mutex::Lock _l(_statistics_m); if (verb != Packet::VERB_ACK && verb != Packet::VERB_QOS_MEASUREMENT) { if ((packetId & (ZT_PATH_QOS_ACK_PROTOCOL_DIVISOR - 1)) == 0) { @@ -357,6 +361,7 @@ public: */ inline void receivedAck(int64_t now, int32_t ackedBytes) { + DEBUG_INFO(""); _expectingAckAsOf = 0; _unackedBytes = (ackedBytes > _unackedBytes) ? 0 : _unackedBytes - ackedBytes; int64_t timeSinceThroughputEstimate = (now - _lastThroughputEstimation); @@ -401,6 +406,7 @@ public: */ inline void sentAck(int64_t now) { + DEBUG_INFO(""); Mutex::Lock _l(_statistics_m); _inACKRecords.clear(); _packetsReceivedSinceLastAck = 0; @@ -418,6 +424,7 @@ public: */ inline void receivedQoS(int64_t now, int count, uint64_t *rx_id, uint16_t *rx_ts) { + DEBUG_INFO(""); Mutex::Lock _l(_statistics_m); // Look up egress times and compute latency values for each record std::map::iterator it; @@ -442,6 +449,7 @@ public: */ inline int32_t generateQoSPacket(int64_t now, char *qosBuffer) { + DEBUG_INFO(""); Mutex::Lock _l(_statistics_m); int32_t len = 0; std::map::iterator it = _inQoSRecords.begin(); @@ -466,6 +474,7 @@ public: * @param Current time */ inline void sentQoS(int64_t now) { + DEBUG_INFO(""); _packetsReceivedSinceLastQoS = 0; _lastQoSMeasurement = now; } @@ -584,6 +593,7 @@ public: */ inline void processBackgroundPathMeasurements(int64_t now) { if (now - _lastPathQualityComputeTime > ZT_PATH_QUALITY_COMPUTE_INTERVAL) { + DEBUG_INFO(""); Mutex::Lock _l(_statistics_m); _lastPathQualityComputeTime = now; address().toString(_addrString); diff --git a/node/Peer.cpp b/node/Peer.cpp index 21bbfabe2..923b3b476 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -37,6 +37,8 @@ #include "RingBuffer.hpp" #include "Utils.hpp" +#include "../include/ZeroTierDebug.h" + namespace ZeroTier { Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity) : @@ -681,6 +683,8 @@ inline void Peer::processBackgroundPeerTasks(int64_t now) _localMultipathSupported = ((RR->node->getMultipathMode() != ZT_MULTIPATH_NONE) && (ZT_PROTO_VERSION > 9)); _remoteMultipathSupported = _vProto > 9; // If both peers support multipath and more than one path exist, we can use multipath logic + DEBUG_INFO("from=%llx, _localMultipathSupported=%d, _remoteMultipathSupported=%d, (_uniqueAlivePathCount > 1)=%d", + this->_id.address().toInt(), _localMultipathSupported, _remoteMultipathSupported, (_uniqueAlivePathCount > 1)); _canUseMultipath = _localMultipathSupported && _remoteMultipathSupported && (_uniqueAlivePathCount > 1); } } From 12f2df55863943366cc8f15d7e0826cd7f3d7ac2 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 7 Aug 2018 12:39:06 -0700 Subject: [PATCH 003/362] uncommented status fields --- service/OneService.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/service/OneService.cpp b/service/OneService.cpp index 9b12f17b2..76a1be081 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -306,18 +306,18 @@ static void _peerAggregateLinkToJson(nlohmann::json &pj,const ZT_Peer *peer) nlohmann::json pa = nlohmann::json::array(); for(unsigned int i=0;ipathCount;++i) { - //int64_t lastSend = peer->paths[i].lastSend; - //int64_t lastReceive = peer->paths[i].lastReceive; + int64_t lastSend = peer->paths[i].lastSend; + int64_t lastReceive = peer->paths[i].lastReceive; nlohmann::json j; j["address"] = reinterpret_cast(&(peer->paths[i].address))->toString(tmp); - //j["lastSend"] = (lastSend < 0) ? 0 : lastSend; - //j["lastReceive"] = (lastReceive < 0) ? 0 : lastReceive; + j["lastSend"] = (lastSend < 0) ? 0 : lastSend; + j["lastReceive"] = (lastReceive < 0) ? 0 : lastReceive; //j["trustedPathId"] = peer->paths[i].trustedPathId; //j["active"] = (bool)(peer->paths[i].expired == 0); //j["expired"] = (bool)(peer->paths[i].expired != 0); //j["preferred"] = (bool)(peer->paths[i].preferred != 0); j["latency"] = peer->paths[i].latency; - //j["packetDelayVariance"] = peer->paths[i].packetDelayVariance; + j["pdv"] = peer->paths[i].packetDelayVariance; //j["throughputDisturbCoeff"] = peer->paths[i].throughputDisturbCoeff; //j["packetErrorRatio"] = peer->paths[i].packetErrorRatio; //j["packetLossRatio"] = peer->paths[i].packetLossRatio; From 1e66854b59d831f05dac52e94d71ff07f1a4fe28 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 7 Aug 2018 12:57:40 -0700 Subject: [PATCH 004/362] Temporarily added SO_REUSEADDR to netlink binding code --- osdep/LinuxNetLink.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osdep/LinuxNetLink.cpp b/osdep/LinuxNetLink.cpp index 634126e79..8c2ea7bf9 100644 --- a/osdep/LinuxNetLink.cpp +++ b/osdep/LinuxNetLink.cpp @@ -66,6 +66,8 @@ LinuxNetLink::LinuxNetLink() // set socket timeout to 1 sec so we're not permablocking recv() calls _setSocketTimeout(_fd, 1); + int yes=1; + setsockopt(_fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); _la.nl_family = AF_NETLINK; _la.nl_pid = getpid()+1; @@ -442,6 +444,8 @@ void LinuxNetLink::_linkDeleted(struct nlmsghdr *nlp) void LinuxNetLink::_requestIPv4Routes() { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + int yes=1; + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; @@ -494,6 +498,8 @@ void LinuxNetLink::_requestIPv4Routes() void LinuxNetLink::_requestIPv6Routes() { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + int yes=1; + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; @@ -546,6 +552,8 @@ void LinuxNetLink::_requestIPv6Routes() void LinuxNetLink::_requestInterfaceList() { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + int yes=1; + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; @@ -596,6 +604,8 @@ void LinuxNetLink::_requestInterfaceList() void LinuxNetLink::addRoute(const InetAddress &target, const InetAddress &via, const InetAddress &src, const char *ifaceName) { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + int yes=1; + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; @@ -718,6 +728,8 @@ void LinuxNetLink::addRoute(const InetAddress &target, const InetAddress &via, c void LinuxNetLink::delRoute(const InetAddress &target, const InetAddress &via, const InetAddress &src, const char *ifaceName) { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + int yes=1; + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; @@ -838,6 +850,8 @@ void LinuxNetLink::delRoute(const InetAddress &target, const InetAddress &via, c void LinuxNetLink::addAddress(const InetAddress &addr, const char *iface) { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + int yes=1; + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; @@ -946,6 +960,8 @@ void LinuxNetLink::addAddress(const InetAddress &addr, const char *iface) void LinuxNetLink::removeAddress(const InetAddress &addr, const char *iface) { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + int yes=1; + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; From 3c7e25ed58f8b622146b9ebd82e1938f9df24d36 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 12 Aug 2019 17:04:27 -0700 Subject: [PATCH 005/362] Added call to computeAggregateAllocation() in multipath mode=1 to give realtime allocation output --- node/Peer.cpp | 14 +++++++++++--- node/Peer.hpp | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/node/Peer.cpp b/node/Peer.cpp index 8a5bf40c2..838136968 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -275,7 +275,7 @@ void Peer::recordIncomingPacket(void *tPtr, const SharedPtr &path, const u } } -void Peer::computeAggregateProportionalAllocation(int64_t now) +void Peer::computeAggregateAllocation(int64_t now) { float maxStability = 0; float totalRelativeQuality = 0; @@ -318,7 +318,13 @@ void Peer::computeAggregateProportionalAllocation(int64_t now) // Convert set of relative performances into an allocation set for(uint16_t i=0;iupdateComponentAllocationOfAggregateLink((unsigned char)((_paths[i].p->relativeQuality() / totalRelativeQuality) * 255)); + + if (RR->node->getMultipathMode() == ZT_MULTIPATH_RANDOM) { + _paths[i].p->updateComponentAllocationOfAggregateLink(((float)_pathChoiceHist.countValue(i) / (float)_pathChoiceHist.count()) * 255); + } + if (RR->node->getMultipathMode() == ZT_MULTIPATH_PROPORTIONALLY_BALANCED) { + _paths[i].p->updateComponentAllocationOfAggregateLink((unsigned char)((_paths[i].p->relativeQuality() / totalRelativeQuality) * 255)); + } } } } @@ -415,6 +421,7 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired) int numAlivePaths = 0; int numStalePaths = 0; if (RR->node->getMultipathMode() == ZT_MULTIPATH_RANDOM) { + computeAggregateAllocation(now); /* This call is algorithmically inert but gives us a value to show in the status output */ int alivePaths[ZT_MAX_PEER_NETWORK_PATHS]; int stalePaths[ZT_MAX_PEER_NETWORK_PATHS]; memset(&alivePaths, -1, sizeof(alivePaths)); @@ -434,6 +441,7 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired) unsigned int r = _freeRandomByte; if (numAlivePaths > 0) { int rf = r % numAlivePaths; + _pathChoiceHist.push(alivePaths[rf]); // Record which path we chose return _paths[alivePaths[rf]].p; } else if(numStalePaths > 0) { @@ -449,7 +457,7 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired) if (RR->node->getMultipathMode() == ZT_MULTIPATH_PROPORTIONALLY_BALANCED) { if ((now - _lastAggregateAllocation) >= ZT_PATH_QUALITY_COMPUTE_INTERVAL) { _lastAggregateAllocation = now; - computeAggregateProportionalAllocation(now); + computeAggregateAllocation(now); } // Randomly choose path according to their allocations float rf = _freeRandomByte; diff --git a/node/Peer.hpp b/node/Peer.hpp index b4cbe0572..6d3ce553e 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -194,7 +194,7 @@ public: * * @param now Current time */ - void computeAggregateProportionalAllocation(int64_t now); + void computeAggregateAllocation(int64_t now); /** * @return The aggregate link Packet Delay Variance (PDV) From d8ce1f7914c462f4731ba009520d3cc526dfcc12 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 13 Aug 2019 12:41:30 -0700 Subject: [PATCH 006/362] Added ZT_DIRECT_PATH_PUSH_INTERVAL_MULTIPATH to decrease link aggregation time --- node/Constants.hpp | 9 +++++++++ node/Peer.cpp | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/node/Constants.hpp b/node/Constants.hpp index d58e408f3..16be0c206 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -546,6 +546,15 @@ */ #define ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH 120000 +/** + * Interval between direct path pushes in milliseconds if we are currently in multipath + * mode. In this mode the distinction between ZT_DIRECT_PATH_PUSH_INTERVAL and + * ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH does not exist since we want to inform other + * peers of this peer's new link/address as soon as possible so that both peers can + * begin forming an aggregated link. + */ +#define ZT_DIRECT_PATH_PUSH_INTERVAL_MULTIPATH ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH / 16 + /** * Time horizon for push direct paths cutoff */ diff --git a/node/Peer.cpp b/node/Peer.cpp index 838136968..a7c8bf1af 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -210,7 +210,8 @@ void Peer::received( // is done less frequently. if (this->trustEstablished(now)) { const int64_t sinceLastPush = now - _lastDirectPathPushSent; - if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH : ZT_DIRECT_PATH_PUSH_INTERVAL)) { + if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH : ZT_DIRECT_PATH_PUSH_INTERVAL) + || (_canUseMultipath && (sinceLastPush >= (ZT_DIRECT_PATH_PUSH_INTERVAL_MULTIPATH)))) { _lastDirectPathPushSent = now; std::vector pathsToPush(RR->node->directPaths()); if (pathsToPush.size() > 0) { From 36d368cb78c4c352ec309dd9ba7c4cd8d434a725 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 13 Aug 2019 13:26:41 -0700 Subject: [PATCH 007/362] Check for (local multipath support only) during decision to decrease direct path push interval. This prevents the chicken-and-egg situation of not knowing if two peers can support multipath on both ends and thusly not sending eachother their direct paths quickly enough. --- node/Constants.hpp | 2 +- node/Peer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/node/Constants.hpp b/node/Constants.hpp index 16be0c206..010b69684 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -553,7 +553,7 @@ * peers of this peer's new link/address as soon as possible so that both peers can * begin forming an aggregated link. */ -#define ZT_DIRECT_PATH_PUSH_INTERVAL_MULTIPATH ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH / 16 +#define ZT_DIRECT_PATH_PUSH_INTERVAL_MULTIPATH (ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH / 16) /** * Time horizon for push direct paths cutoff diff --git a/node/Peer.cpp b/node/Peer.cpp index a7c8bf1af..c1bfc1707 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -211,7 +211,7 @@ void Peer::received( if (this->trustEstablished(now)) { const int64_t sinceLastPush = now - _lastDirectPathPushSent; if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH : ZT_DIRECT_PATH_PUSH_INTERVAL) - || (_canUseMultipath && (sinceLastPush >= (ZT_DIRECT_PATH_PUSH_INTERVAL_MULTIPATH)))) { + || (_localMultipathSupported && (sinceLastPush >= (ZT_DIRECT_PATH_PUSH_INTERVAL_MULTIPATH)))) { _lastDirectPathPushSent = now; std::vector pathsToPush(RR->node->directPaths()); if (pathsToPush.size() > 0) { From 2593c6efee84f7bc1d6ca85aa19a3f2345b29ceb Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 13 Aug 2019 14:34:11 -0700 Subject: [PATCH 008/362] Adjusted multipath constants --- node/Constants.hpp | 4 ++-- node/Peer.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/node/Constants.hpp b/node/Constants.hpp index 010b69684..3f95ac29c 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -401,7 +401,7 @@ /** * How often an aggregate link statistics report is emitted into this tracing system */ -#define ZT_PATH_AGGREGATE_STATS_REPORT_INTERVAL 60000 +#define ZT_PATH_AGGREGATE_STATS_REPORT_INTERVAL 30000 /** * How much an aggregate link's component paths can vary from their target allocation @@ -467,7 +467,7 @@ * by default to avoid increasing idle bandwidth use for regular * links. */ -#define ZT_MULTIPATH_PEER_PING_PERIOD 5000 +#define ZT_MULTIPATH_PEER_PING_PERIOD (ZT_PEER_PING_PERIOD / 10) /** * Paths are considered expired if they have not sent us a real packet in this long diff --git a/node/Peer.cpp b/node/Peer.cpp index c1bfc1707..2bba9bd9b 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -782,9 +782,6 @@ unsigned int Peer::doPingAndKeepalive(void *tPtr,int64_t now) unsigned int sent = 0; Mutex::Lock _l(_paths_m); - const bool sendFullHello = ((now - _lastSentFullHello) >= ZT_PEER_PING_PERIOD); - _lastSentFullHello = now; - processBackgroundPeerTasks(now); // Emit traces regarding aggregate link status @@ -815,6 +812,9 @@ unsigned int Peer::doPingAndKeepalive(void *tPtr,int64_t now) else break; } + const bool sendFullHello = ((now - _lastSentFullHello) >= ZT_PEER_PING_PERIOD); + _lastSentFullHello = now; + unsigned int j = 0; for(unsigned int i=0;i Date: Tue, 13 Aug 2019 14:34:47 -0700 Subject: [PATCH 009/362] More informative link aggregation trace outputs --- node/Trace.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/node/Trace.cpp b/node/Trace.cpp index cccab9c91..6e0b9f05d 100644 --- a/node/Trace.cpp +++ b/node/Trace.cpp @@ -109,17 +109,22 @@ void Trace::peerConfirmingUnknownPath(void *const tPtr,const uint64_t networkId, void Trace::peerLinkNowRedundant(void *const tPtr,Peer &peer) { - ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is fully redundant",peer.address().toInt()); + if ((RR->node->getMultipathMode() != ZT_MULTIPATH_RANDOM)) { + ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is now a randomly-distributed aggregate link",peer.address().toInt()); + } + if ((RR->node->getMultipathMode() != ZT_MULTIPATH_PROPORTIONALLY_BALANCED)) { + ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is now a proportionally-balanced aggregate link",peer.address().toInt()); + } } void Trace::peerLinkNoLongerRedundant(void *const tPtr,Peer &peer) { - ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is no longer redundant",peer.address().toInt()); + ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx has degraded and is no longer an aggregate link",peer.address().toInt()); } void Trace::peerLinkAggregateStatistics(void *const tPtr,Peer &peer) { - ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is composed of (%d) physical paths %s, has packet delay variance (%.0f ms), mean latency (%.0f ms)", + ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is composed of (%d) physical paths %s, has PDV (%.0f ms), mean latency (%.0f ms)", peer.address().toInt(), peer.aggregateLinkPhysicalPathCount(), peer.interfaceListStr(), From 5b7d60f5cdd9157cb7d0beb3a82a2ceee7ee4c9c Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 13 Aug 2019 14:42:48 -0700 Subject: [PATCH 010/362] Whoops --- node/Trace.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/Trace.cpp b/node/Trace.cpp index 6e0b9f05d..8e98cb072 100644 --- a/node/Trace.cpp +++ b/node/Trace.cpp @@ -109,10 +109,10 @@ void Trace::peerConfirmingUnknownPath(void *const tPtr,const uint64_t networkId, void Trace::peerLinkNowRedundant(void *const tPtr,Peer &peer) { - if ((RR->node->getMultipathMode() != ZT_MULTIPATH_RANDOM)) { + if ((RR->node->getMultipathMode() == ZT_MULTIPATH_RANDOM)) { ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is now a randomly-distributed aggregate link",peer.address().toInt()); } - if ((RR->node->getMultipathMode() != ZT_MULTIPATH_PROPORTIONALLY_BALANCED)) { + if ((RR->node->getMultipathMode() == ZT_MULTIPATH_PROPORTIONALLY_BALANCED)) { ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is now a proportionally-balanced aggregate link",peer.address().toInt()); } } From b0e86d11c947d080b3fd13d7d6a8e2b3745150cb Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Wed, 14 Aug 2019 11:24:03 -0700 Subject: [PATCH 011/362] Minor. Name change for trace functions --- node/Peer.cpp | 4 ++-- node/Trace.cpp | 4 ++-- node/Trace.hpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/node/Peer.cpp b/node/Peer.cpp index 2bba9bd9b..ce3083cc1 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -794,10 +794,10 @@ unsigned int Peer::doPingAndKeepalive(void *tPtr,int64_t now) } } if (alivePathCount < 2 && _linkIsRedundant) { _linkIsRedundant = !_linkIsRedundant; - RR->t->peerLinkNoLongerRedundant(NULL,*this); + RR->t->peerLinkNoLongerAggregate(NULL,*this); } if (alivePathCount > 1 && !_linkIsRedundant) { _linkIsRedundant = !_linkIsRedundant; - RR->t->peerLinkNowRedundant(NULL,*this); + RR->t->peerLinkNoLongerAggregate(NULL,*this); } } diff --git a/node/Trace.cpp b/node/Trace.cpp index 8e98cb072..b7c002719 100644 --- a/node/Trace.cpp +++ b/node/Trace.cpp @@ -107,7 +107,7 @@ void Trace::peerConfirmingUnknownPath(void *const tPtr,const uint64_t networkId, } } -void Trace::peerLinkNowRedundant(void *const tPtr,Peer &peer) +void Trace::peerLinkNowAggregate(void *const tPtr,Peer &peer) { if ((RR->node->getMultipathMode() == ZT_MULTIPATH_RANDOM)) { ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is now a randomly-distributed aggregate link",peer.address().toInt()); @@ -117,7 +117,7 @@ void Trace::peerLinkNowRedundant(void *const tPtr,Peer &peer) } } -void Trace::peerLinkNoLongerRedundant(void *const tPtr,Peer &peer) +void Trace::peerLinkNoLongerAggregate(void *const tPtr,Peer &peer) { ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx has degraded and is no longer an aggregate link",peer.address().toInt()); } diff --git a/node/Trace.hpp b/node/Trace.hpp index 2effb7f0e..162df1542 100644 --- a/node/Trace.hpp +++ b/node/Trace.hpp @@ -122,8 +122,8 @@ public: void peerConfirmingUnknownPath(void *const tPtr,const uint64_t networkId,Peer &peer,const SharedPtr &path,const uint64_t packetId,const Packet::Verb verb); - void peerLinkNowRedundant(void *const tPtr,Peer &peer); - void peerLinkNoLongerRedundant(void *const tPtr,Peer &peer); + void peerLinkNowAggregate(void *const tPtr,Peer &peer); + void peerLinkNoLongerAggregate(void *const tPtr,Peer &peer); void peerLinkAggregateStatistics(void *const tPtr,Peer &peer); From 0634214f2ca1f16572b4313eefc05c879d300d18 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 19 Aug 2019 21:52:33 -0700 Subject: [PATCH 012/362] Added notion of Flows --- include/ZeroTierOne.h | 48 ++++++--- node/Constants.hpp | 6 ++ node/Path.hpp | 8 -- node/Peer.cpp | 215 +++++++++++++++++++++++++++++++------- node/Peer.hpp | 47 ++++++++- node/Switch.cpp | 232 +++++++++++++++++++++++++++++++----------- node/Switch.hpp | 12 ++- node/Trace.cpp | 4 +- 8 files changed, 449 insertions(+), 123 deletions(-) diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index b0be01056..5355cd3e2 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -434,27 +434,49 @@ enum ZT_ResultCode enum ZT_MultipathMode { /** - * No active multipath. - * - * Traffic is merely sent over the strongest path. That being - * said, this mode will automatically failover in the event that a link goes down. + * No fault tolerance or balancing. */ ZT_MULTIPATH_NONE = 0, /** - * Traffic is randomly distributed among all active paths. - * - * Will cease sending traffic over links that appear to be stale. + * Sends traffic out on all paths. */ - ZT_MULTIPATH_RANDOM = 1, + ZT_MULTIPATH_BROADCAST = 1, /** - * Traffic is allocated across all active paths in proportion to their strength and - * reliability. - * - * Will cease sending traffic over links that appear to be stale. + * Sends traffic out on only one path at a time. Immediate fail-over. */ - ZT_MULTIPATH_PROPORTIONALLY_BALANCED = 2, + ZT_MULTIPATH_ACTIVE_BACKUP= 2, + + /** + * Sends traffic out on all interfaces according to a uniform random distribution. + */ + ZT_MULTIPATH_BALANCE_RANDOM = 3, + + /** + * Stripes packets across all paths. + */ + ZT_MULTIPATH_BALANCE_RR_OPAQUE = 4, + + /** + * Balances flows across all paths. + */ + ZT_MULTIPATH_BALANCE_RR_FLOW = 5, + + /** + * Hashes flows across all paths. + */ + ZT_MULTIPATH_BALANCE_XOR_FLOW = 6, + + /** + * Balances traffic across all paths according to observed performance. + */ + ZT_MULTIPATH_BALANCE_DYNAMIC_OPAQUE = 7, + + /** + * Balances flows across all paths. + */ + ZT_MULTIPATH_BALANCE_DYNAMIC_FLOW = 8, }; /** diff --git a/node/Constants.hpp b/node/Constants.hpp index 3f95ac29c..7f962851a 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -266,6 +266,12 @@ */ #define ZT_LOCAL_CONF_FILE_CHECK_INTERVAL 10000 +/** + * How long before we consider a flow to be dead and remove it from the balancing + * policy's list. + */ +#define ZT_MULTIPATH_FLOW_EXPIRATION 60000 + /** * How frequently to check for changes to the system's network interfaces. When * the service decides to use this constant it's because we want to react more diff --git a/node/Path.hpp b/node/Path.hpp index bc8d7dc5a..bc28c7341 100644 --- a/node/Path.hpp +++ b/node/Path.hpp @@ -308,7 +308,6 @@ public: */ inline void recordOutgoingPacket(int64_t now, int64_t packetId, uint16_t payloadLength, Packet::Verb verb) { - DEBUG_INFO(""); Mutex::Lock _l(_statistics_m); if (verb != Packet::VERB_ACK && verb != Packet::VERB_QOS_MEASUREMENT) { if ((packetId & (ZT_PATH_QOS_ACK_PROTOCOL_DIVISOR - 1)) == 0) { @@ -332,7 +331,6 @@ public: */ inline void recordIncomingPacket(int64_t now, int64_t packetId, uint16_t payloadLength, Packet::Verb verb) { - DEBUG_INFO(""); Mutex::Lock _l(_statistics_m); if (verb != Packet::VERB_ACK && verb != Packet::VERB_QOS_MEASUREMENT) { if ((packetId & (ZT_PATH_QOS_ACK_PROTOCOL_DIVISOR - 1)) == 0) { @@ -353,7 +351,6 @@ public: */ inline void receivedAck(int64_t now, int32_t ackedBytes) { - DEBUG_INFO(""); _expectingAckAsOf = 0; _unackedBytes = (ackedBytes > _unackedBytes) ? 0 : _unackedBytes - ackedBytes; int64_t timeSinceThroughputEstimate = (now - _lastThroughputEstimation); @@ -398,7 +395,6 @@ public: */ inline void sentAck(int64_t now) { - DEBUG_INFO(""); Mutex::Lock _l(_statistics_m); _inACKRecords.clear(); _packetsReceivedSinceLastAck = 0; @@ -416,7 +412,6 @@ public: */ inline void receivedQoS(int64_t now, int count, uint64_t *rx_id, uint16_t *rx_ts) { - DEBUG_INFO(""); Mutex::Lock _l(_statistics_m); // Look up egress times and compute latency values for each record std::map::iterator it; @@ -441,7 +436,6 @@ public: */ inline int32_t generateQoSPacket(int64_t now, char *qosBuffer) { - DEBUG_INFO(""); Mutex::Lock _l(_statistics_m); int32_t len = 0; std::map::iterator it = _inQoSRecords.begin(); @@ -466,7 +460,6 @@ public: * @param Current time */ inline void sentQoS(int64_t now) { - DEBUG_INFO(""); _packetsReceivedSinceLastQoS = 0; _lastQoSMeasurement = now; } @@ -586,7 +579,6 @@ public: inline void processBackgroundPathMeasurements(const int64_t now) { if (now - _lastPathQualityComputeTime > ZT_PATH_QUALITY_COMPUTE_INTERVAL) { - DEBUG_INFO(""); Mutex::Lock _l(_statistics_m); _lastPathQualityComputeTime = now; address().toString(_addrString); diff --git a/node/Peer.cpp b/node/Peer.cpp index ce3083cc1..d1ef9ecf6 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -75,7 +75,9 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident _linkIsRedundant(false), _remotePeerMultipathEnabled(false), _lastAggregateStatsReport(0), - _lastAggregateAllocation(0) + _lastAggregateAllocation(0), + _virtualPathCount(0), + _roundRobinPathAssignmentIdx(0) { if (!myIdentity.agree(peerIdentity,_key,ZT_PEER_SECRET_KEY_LENGTH)) throw ZT_EXCEPTION_INVALID_ARGUMENT; @@ -195,6 +197,9 @@ void Peer::received( } else { attemptToContact = true; } + + // Every time we learn of new path, rebuild set of virtual paths + constructSetOfVirtualPaths(); } } @@ -256,6 +261,39 @@ void Peer::received( } } +void Peer::constructSetOfVirtualPaths() +{ + if (!_remoteMultipathSupported) { + return; + } + Mutex::Lock _l(_virtual_paths_m); + + int64_t now = RR->node->now(); + _virtualPathCount = 0; + for(unsigned int i=0;ialive(now)) { + for(unsigned int j=0;jalive(now)) { + int64_t localSocket = _paths[j].p->localSocket(); + bool foundVirtualPath = false; + for (int k=0; k<_virtualPaths.size(); k++) { + if (_virtualPaths[k]->localSocket == localSocket && _virtualPaths[k]->p == _paths[i].p) { + foundVirtualPath = true; + } + } + if (!foundVirtualPath) + { + VirtualPath *np = new VirtualPath; + np->p = _paths[i].p; + np->localSocket = localSocket; + _virtualPaths.push_back(np); + } + } + } + } + } +} + void Peer::recordOutgoingPacket(const SharedPtr &path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, int64_t now) { @@ -320,10 +358,10 @@ void Peer::computeAggregateAllocation(int64_t now) for(uint16_t i=0;inode->getMultipathMode() == ZT_MULTIPATH_RANDOM) { + if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_RANDOM) { _paths[i].p->updateComponentAllocationOfAggregateLink(((float)_pathChoiceHist.countValue(i) / (float)_pathChoiceHist.count()) * 255); } - if (RR->node->getMultipathMode() == ZT_MULTIPATH_PROPORTIONALLY_BALANCED) { + if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_DYNAMIC_OPAQUE) { _paths[i].p->updateComponentAllocationOfAggregateLink((unsigned char)((_paths[i].p->relativeQuality() / totalRelativeQuality) * 255)); } } @@ -382,9 +420,22 @@ int Peer::aggregateLinkLogicalPathCount() return pathCount; } -SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired) +std::vector> Peer::getAllPaths(int64_t now) +{ + Mutex::Lock _l(_virtual_paths_m); // FIXME: TX can now lock RX + std::vector> paths; + for (int i=0; i<_virtualPaths.size(); i++) { + if (_virtualPaths[i]->p) { + paths.push_back(_virtualPaths[i]->p); + } + } + return paths; +} + +SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired, int64_t flowId) { Mutex::Lock _l(_paths_m); + SharedPtr selectedPath; unsigned int bestPath = ZT_MAX_PEER_NETWORK_PATHS; /** @@ -410,52 +461,129 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired) return SharedPtr(); } + // Update path measurements for(unsigned int i=0;iprocessBackgroundPathMeasurements(now); } } + // Detect new flows and update existing records + if (_flows.count(flowId)) { + _flows[flowId]->lastSend = now; + } + else { + fprintf(stderr, "new flow %llx detected between this node and %llx (%lu active flow(s))\n", + flowId, this->_id.address().toInt(), (_flows.size()+1)); + struct Flow *newFlow = new Flow(flowId, now); + _flows[flowId] = newFlow; + newFlow->assignedPath = nullptr; + } + // Construct set of virtual paths if needed + if (!_virtualPaths.size()) { + constructSetOfVirtualPaths(); + } + if (!_virtualPaths.size()) { + fprintf(stderr, "no paths to send packet out on\n"); + return SharedPtr(); + } /** - * Randomly distribute traffic across all paths + * Traffic is randomly distributed among all active paths. */ int numAlivePaths = 0; int numStalePaths = 0; - if (RR->node->getMultipathMode() == ZT_MULTIPATH_RANDOM) { - computeAggregateAllocation(now); /* This call is algorithmically inert but gives us a value to show in the status output */ - int alivePaths[ZT_MAX_PEER_NETWORK_PATHS]; - int stalePaths[ZT_MAX_PEER_NETWORK_PATHS]; - memset(&alivePaths, -1, sizeof(alivePaths)); - memset(&stalePaths, -1, sizeof(stalePaths)); - for(unsigned int i=0;ialive(now)) { - alivePaths[numAlivePaths] = i; - numAlivePaths++; - } - else { - stalePaths[numStalePaths] = i; - numStalePaths++; + if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_RANDOM) { + int sz = _virtualPaths.size(); + if (sz) { + int idx = _freeRandomByte % sz; + _pathChoiceHist.push(idx); + char pathStr[128]; + _virtualPaths[idx]->p->address().toString(pathStr); + fprintf(stderr, "sending out: (%llx), idx=%d: path=%s, localSocket=%lld\n", + this->_id.address().toInt(), idx, pathStr, _virtualPaths[idx]->localSocket); + return _virtualPaths[idx]->p; + } + // This call is algorithmically inert but gives us a value to show in the status output + computeAggregateAllocation(now); + } + + /** + * All traffic is sent on all paths. + */ + if (RR->node->getMultipathMode() == ZT_MULTIPATH_BROADCAST) { + // Not handled here. Handled in Switch.cpp + } + + /** + * Only one link is active. Fail-over is immediate. + */ + if (RR->node->getMultipathMode() == ZT_MULTIPATH_ACTIVE_BACKUP) { + // fprintf(stderr, "ZT_MULTIPATH_ACTIVE_BACKUP\n"); + } + + /** + * Packets are striped across all available paths. + */ + if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_RR_OPAQUE) { + // fprintf(stderr, "ZT_MULTIPATH_BALANCE_RR_OPAQUE\n"); + int16_t previousIdx = _roundRobinPathAssignmentIdx; + if (_roundRobinPathAssignmentIdx < (_virtualPaths.size()-1)) { + _roundRobinPathAssignmentIdx++; + } + else { + _roundRobinPathAssignmentIdx = 0; + } + selectedPath = _virtualPaths[previousIdx]->p; + char pathStr[128]; + selectedPath->address().toString(pathStr); + fprintf(stderr, "sending packet out on path %s at index %d\n", + pathStr, previousIdx); + return selectedPath; + } + + /** + * Flows are striped across all available paths. + */ + if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_RR_FLOW) { + // fprintf(stderr, "ZT_MULTIPATH_BALANCE_RR_FLOW\n"); + } + + /** + * Flows are hashed across all available paths. + */ + if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_XOR_FLOW) { + // fprintf(stderr, "ZT_MULTIPATH_BALANCE_XOR_FLOW (%llx) \n", flowId); + char pathStr[128]; + struct Flow *currFlow = NULL; + if (_flows.count(flowId)) { + currFlow = _flows[flowId]; + if (!currFlow->assignedPath) { + int idx = abs((int)(currFlow->flowId % (_virtualPaths.size()-1))); + currFlow->assignedPath = _virtualPaths[idx]; + _virtualPaths[idx]->p->address().toString(pathStr); + fprintf(stderr, "assigning flow %llx between this node and peer %llx to path %s at index %d\n", + currFlow->flowId, this->_id.address().toInt(), pathStr, idx); + } + else { + if (!currFlow->assignedPath->p->alive(now)) { + char newPathStr[128]; + currFlow->assignedPath->p->address().toString(pathStr); + // Re-assign + int idx = abs((int)(currFlow->flowId % (_virtualPaths.size()-1))); + currFlow->assignedPath = _virtualPaths[idx]; + _virtualPaths[idx]->p->address().toString(newPathStr); + fprintf(stderr, "path %s assigned to flow %llx between this node and %llx appears to be dead, reassigning to path %s\n", + pathStr, currFlow->flowId, this->_id.address().toInt(), newPathStr); } } - } - unsigned int r = _freeRandomByte; - if (numAlivePaths > 0) { - int rf = r % numAlivePaths; - _pathChoiceHist.push(alivePaths[rf]); // Record which path we chose - return _paths[alivePaths[rf]].p; - } - else if(numStalePaths > 0) { - // Resort to trying any non-expired path - int rf = r % numStalePaths; - return _paths[stalePaths[rf]].p; + return currFlow->assignedPath->p; } } /** - * Proportionally allocate traffic according to dynamic path quality measurements + * Proportionally allocate traffic according to dynamic path quality measurements. */ - if (RR->node->getMultipathMode() == ZT_MULTIPATH_PROPORTIONALLY_BALANCED) { + if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_DYNAMIC_OPAQUE) { if ((now - _lastAggregateAllocation) >= ZT_PATH_QUALITY_COMPUTE_INTERVAL) { _lastAggregateAllocation = now; computeAggregateAllocation(now); @@ -476,6 +604,13 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired) return _paths[bestPath].p; } } + + /** + * Flows are dynamically allocated across paths in proportion to link strength and load. + */ + if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_DYNAMIC_FLOW) { + } + return SharedPtr(); } @@ -676,10 +811,20 @@ inline void Peer::processBackgroundPeerTasks(const int64_t now) _localMultipathSupported = ((RR->node->getMultipathMode() != ZT_MULTIPATH_NONE) && (ZT_PROTO_VERSION > 9)); _remoteMultipathSupported = _vProto > 9; // If both peers support multipath and more than one path exist, we can use multipath logic - DEBUG_INFO("from=%llx, _localMultipathSupported=%d, _remoteMultipathSupported=%d, (_uniqueAlivePathCount > 1)=%d", - this->_id.address().toInt(), _localMultipathSupported, _remoteMultipathSupported, (_uniqueAlivePathCount > 1)); _canUseMultipath = _localMultipathSupported && _remoteMultipathSupported && (_uniqueAlivePathCount > 1); } + + // Remove old flows + std::map::iterator it = _flows.begin(); + while (it != _flows.end()) { + if ((now - it->second->lastSend) > ZT_MULTIPATH_FLOW_EXPIRATION) { + fprintf(stderr, "forgetting flow %llx between this node and %llx (%lu active flow(s))\n", + it->first, this->_id.address().toInt(), _flows.size()); + it = _flows.erase(it); + } else { + it++; + } + } } void Peer::sendACK(void *tPtr,const SharedPtr &path,const int64_t localSocket,const InetAddress &atAddress,int64_t now) diff --git a/node/Peer.hpp b/node/Peer.hpp index 6d3ce553e..7633ad7d5 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -28,6 +28,8 @@ #define ZT_PEER_HPP #include +#include +#include #include "../include/ZeroTierOne.h" @@ -147,6 +149,8 @@ public: return false; } + void constructSetOfVirtualPaths(); + /** * Record statistics on outgoing packets * @@ -216,14 +220,17 @@ public: */ int aggregateLinkLogicalPathCount(); + std::vector> getAllPaths(int64_t now); + /** * Get the most appropriate direct path based on current multipath and QoS configuration * * @param now Current time + * @param flowId Session-specific protocol flow identifier used for path allocation * @param includeExpired If true, include even expired paths * @return Best current path or NULL if none */ - SharedPtr getAppropriatePath(int64_t now, bool includeExpired); + SharedPtr getAppropriatePath(int64_t now, bool includeExpired, int64_t flowId = -1); /** * Generate a human-readable string of interface names making up the aggregate link, also include @@ -680,6 +687,44 @@ private: int64_t _lastAggregateAllocation; char _interfaceListStr[256]; // 16 characters * 16 paths in a link + + // + struct LinkPerformanceEntry + { + int64_t packetId; + struct VirtualPath *egressVirtualPath; + struct VirtualPath *ingressVirtualPath; + }; + + // Virtual paths + int _virtualPathCount; + Mutex _virtual_paths_m; + struct VirtualPath + { + SharedPtr p; + int64_t localSocket; + std::queue performanceEntries; + }; + std::vector _virtualPaths; + + // Flows + struct Flow + { + Flow(int64_t fid, int64_t ls) : + flowId(fid), + lastSend(ls), + assignedPath(NULL) + {} + + int64_t flowId; + int64_t bytesPerSecond; + int64_t lastSend; + struct VirtualPath *assignedPath; + }; + + std::map _flows; + + int16_t _roundRobinPathAssignmentIdx; }; } // namespace ZeroTier diff --git a/node/Switch.cpp b/node/Switch.cpp index a6852d9f4..c2251f23d 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -255,6 +255,35 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre } catch ( ... ) {} // sanity check, should be caught elsewhere } +// Returns true if packet appears valid; pos and proto will be set +static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsigned int &pos,unsigned int &proto) +{ + if (frameLen < 40) + return false; + pos = 40; + proto = frameData[6]; + while (pos <= frameLen) { + switch(proto) { + case 0: // hop-by-hop options + case 43: // routing + case 60: // destination options + case 135: // mobility options + if ((pos + 8) > frameLen) + return false; // invalid! + proto = frameData[pos]; + pos += ((unsigned int)frameData[pos + 1] * 8) + 8; + break; + + //case 44: // fragment -- we currently can't parse these and they are deprecated in IPv6 anyway + //case 50: + //case 51: // IPSec ESP and AH -- we have to stop here since this is encrypted stuff + default: + return true; + } + } + return false; // overflow == invalid +} + void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) { if (!network->hasConfig()) @@ -271,6 +300,73 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const uint8_t qosBucket = ZT_QOS_DEFAULT_BUCKET; + /* A pseudo-unique identifier used by the balancing and bonding policies to associate properties + * of a specific protocol flow over time and to determine which virtual path this packet + * shall be sent out on. This identifier consists of the source port and destination port + * of the encapsulated frame. + * + * A flowId of -1 will indicate that whatever packet we are about transmit has no + * preferred virtual path and will be sent out according to what the multipath logic + * deems appropriate. An example of this would be an ICMP packet. + */ + int64_t flowId = -1; + + if (etherType == ZT_ETHERTYPE_IPV4 && (len >= 20)) { + uint16_t srcPort = 0; + uint16_t dstPort = 0; + int8_t proto = (reinterpret_cast(data)[9]); + const unsigned int headerLen = 4 * (reinterpret_cast(data)[0] & 0xf); + switch(proto) { + case 0x01: // ICMP + flowId = 0x01; + break; + // All these start with 16-bit source and destination port in that order + case 0x06: // TCP + case 0x11: // UDP + case 0x84: // SCTP + case 0x88: // UDPLite + if (len > (headerLen + 4)) { + unsigned int pos = headerLen + 0; + srcPort = (reinterpret_cast(data)[pos++]) << 8; + srcPort |= (reinterpret_cast(data)[pos]); + pos++; + dstPort = (reinterpret_cast(data)[pos++]) << 8; + dstPort |= (reinterpret_cast(data)[pos]); + flowId = ((int64_t)srcPort << 48) | ((int64_t)dstPort << 32) | proto; + } + break; + } + } + + if (etherType == ZT_ETHERTYPE_IPV6 && (len >= 40)) { + uint16_t srcPort = 0; + uint16_t dstPort = 0; + unsigned int pos; + unsigned int proto; + _ipv6GetPayload((const uint8_t *)data, len, pos, proto); + switch(proto) { + case 0x3A: // ICMPv6 + flowId = 0x3A; + break; + // All these start with 16-bit source and destination port in that order + case 0x06: // TCP + case 0x11: // UDP + case 0x84: // SCTP + case 0x88: // UDPLite + if (len > (pos + 4)) { + srcPort = (reinterpret_cast(data)[pos++]) << 8; + srcPort |= (reinterpret_cast(data)[pos]); + pos++; + dstPort = (reinterpret_cast(data)[pos++]) << 8; + dstPort |= (reinterpret_cast(data)[pos]); + flowId = ((int64_t)srcPort << 48) | ((int64_t)dstPort << 32) | proto; + } + break; + default: + break; + } + } + if (to.isMulticast()) { MulticastGroup multicastGroup(to,0); @@ -280,7 +376,7 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const * otherwise a straightforward Ethernet switch emulation. Vanilla ARP * is dumb old broadcast and simply doesn't scale. ZeroTier multicast * groups have an additional field called ADI (additional distinguishing - * information) which was added specifically for ARP though it could + * information) which was added specifically for ARP though it could * be used for other things too. We then take ARP broadcasts and turn * them into multicasts by stuffing the IP address being queried into * the 32-bit ADI field. In practice this uses our multicast pub/sub @@ -429,7 +525,7 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const outp.append(data,len); if (!network->config().disableCompression()) outp.compress(); - aqm_enqueue(tPtr,network,outp,true,qosBucket); + aqm_enqueue(tPtr,network,outp,true,qosBucket,flowId); } else { Packet outp(toZT,RR->identity.address(),Packet::VERB_FRAME); outp.append(network->id()); @@ -437,7 +533,7 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const outp.append(data,len); if (!network->config().disableCompression()) outp.compress(); - aqm_enqueue(tPtr,network,outp,true,qosBucket); + aqm_enqueue(tPtr,network,outp,true,qosBucket,flowId); } } else { // Destination is bridged behind a remote peer @@ -493,7 +589,7 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const outp.append(data,len); if (!network->config().disableCompression()) outp.compress(); - aqm_enqueue(tPtr,network,outp,true,qosBucket); + aqm_enqueue(tPtr,network,outp,true,qosBucket,flowId); } else { RR->t->outgoingNetworkFrameDropped(tPtr,network,from,to,etherType,vlanId,len,"filter blocked (bridge replication)"); } @@ -501,10 +597,10 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const } } -void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet &packet,bool encrypt,int qosBucket) +void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet &packet,bool encrypt,int qosBucket,int64_t flowId) { if(!network->qosEnabled()) { - send(tPtr, packet, encrypt); + send(tPtr, packet, encrypt, flowId); return; } NetworkQoSControlBlock *nqcb = _netQueueControlBlock[network->id()]; @@ -518,11 +614,9 @@ void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet & nqcb->inactiveQueues.push_back(new ManagedQueue(i)); } } - + // Don't apply QoS scheduling to ZT protocol traffic if (packet.verb() != Packet::VERB_FRAME && packet.verb() != Packet::VERB_EXT_FRAME) { - // DEBUG_INFO("skipping, no QoS for this packet, verb=%x", packet.verb()); - // just send packet normally, no QoS for ZT protocol traffic - send(tPtr, packet, encrypt); + send(tPtr, packet, encrypt, flowId); } _aqm_m.lock(); @@ -530,7 +624,7 @@ void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet & // Enqueue packet and move queue to appropriate list const Address dest(packet.destination()); - TXQueueEntry *txEntry = new TXQueueEntry(dest,RR->node->now(),packet,encrypt); + TXQueueEntry *txEntry = new TXQueueEntry(dest,RR->node->now(),packet,encrypt,flowId); ManagedQueue *selectedQueue = nullptr; for (size_t i=0; ibyteCredit -= len; // Send the packet! queueAtFrontOfList->q.pop_front(); - send(tPtr, entryToEmit->packet, entryToEmit->encrypt); + send(tPtr, entryToEmit->packet, entryToEmit->encrypt, entryToEmit->flowId); (*nqcb).second->_currEnqueuedPackets--; } if (queueAtFrontOfList) { @@ -734,7 +828,7 @@ void Switch::aqm_dequeue(void *tPtr) queueAtFrontOfList->byteLength -= len; queueAtFrontOfList->byteCredit -= len; queueAtFrontOfList->q.pop_front(); - send(tPtr, entryToEmit->packet, entryToEmit->encrypt); + send(tPtr, entryToEmit->packet, entryToEmit->encrypt, entryToEmit->flowId); (*nqcb).second->_currEnqueuedPackets--; } if (queueAtFrontOfList) { @@ -758,18 +852,18 @@ void Switch::removeNetworkQoSControlBlock(uint64_t nwid) } } -void Switch::send(void *tPtr,Packet &packet,bool encrypt) +void Switch::send(void *tPtr,Packet &packet,bool encrypt,int64_t flowId) { const Address dest(packet.destination()); if (dest == RR->identity.address()) return; - if (!_trySend(tPtr,packet,encrypt)) { + if (!_trySend(tPtr,packet,encrypt,flowId)) { { Mutex::Lock _l(_txQueue_m); if (_txQueue.size() >= ZT_TX_QUEUE_SIZE) { _txQueue.pop_front(); } - _txQueue.push_back(TXQueueEntry(dest,RR->node->now(),packet,encrypt)); + _txQueue.push_back(TXQueueEntry(dest,RR->node->now(),packet,encrypt,flowId)); } if (!RR->topology->getPeer(tPtr,dest)) requestWhois(tPtr,RR->node->now(),dest); @@ -791,10 +885,11 @@ void Switch::requestWhois(void *tPtr,const int64_t now,const Address &addr) const SharedPtr upstream(RR->topology->getUpstreamPeer()); if (upstream) { + int64_t flowId = -1; Packet outp(upstream->address(),RR->identity.address(),Packet::VERB_WHOIS); addr.appendTo(outp); RR->node->expectReplyTo(outp.packetId()); - send(tPtr,outp,true); + send(tPtr,outp,true,flowId); } } @@ -819,7 +914,7 @@ void Switch::doAnythingWaitingForPeer(void *tPtr,const SharedPtr &peer) Mutex::Lock _l(_txQueue_m); for(std::list< TXQueueEntry >::iterator txi(_txQueue.begin());txi!=_txQueue.end();) { if (txi->dest == peer->address()) { - if (_trySend(tPtr,txi->packet,txi->encrypt)) { + if (_trySend(tPtr,txi->packet,txi->encrypt,txi->flowId)) { _txQueue.erase(txi++); } else { ++txi; @@ -843,7 +938,7 @@ unsigned long Switch::doTimerTasks(void *tPtr,int64_t now) Mutex::Lock _l(_txQueue_m); for(std::list< TXQueueEntry >::iterator txi(_txQueue.begin());txi!=_txQueue.end();) { - if (_trySend(tPtr,txi->packet,txi->encrypt)) { + if (_trySend(tPtr,txi->packet,txi->encrypt,txi->flowId)) { _txQueue.erase(txi++); } else if ((now - txi->creationTime) > ZT_TRANSMIT_QUEUE_TIMEOUT) { _txQueue.erase(txi++); @@ -907,7 +1002,7 @@ bool Switch::_shouldUnite(const int64_t now,const Address &source,const Address return false; } -bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt) +bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt,int64_t flowId) { SharedPtr viaPath; const int64_t now = RR->node->now(); @@ -915,54 +1010,73 @@ bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt) const SharedPtr peer(RR->topology->getPeer(tPtr,destination)); if (peer) { - viaPath = peer->getAppropriatePath(now,false); - if (!viaPath) { - peer->tryMemorizedPath(tPtr,now); // periodically attempt memorized or statically defined paths, if any are known - const SharedPtr relay(RR->topology->getUpstreamPeer()); - if ( (!relay) || (!(viaPath = relay->getAppropriatePath(now,false))) ) { - if (!(viaPath = peer->getAppropriatePath(now,true))) - return false; + if (RR->node->getMultipathMode() == ZT_MULTIPATH_BROADCAST) { + // Nothing here, we'll grab an entire set of paths to send out on below + } + else { + viaPath = peer->getAppropriatePath(now,false,flowId); + if (!viaPath) { + peer->tryMemorizedPath(tPtr,now); // periodically attempt memorized or statically defined paths, if any are known + const SharedPtr relay(RR->topology->getUpstreamPeer()); + if ( (!relay) || (!(viaPath = relay->getAppropriatePath(now,false,flowId))) ) { + if (!(viaPath = peer->getAppropriatePath(now,true,flowId))) + return false; + } } } } else { return false; } - unsigned int mtu = ZT_DEFAULT_PHYSMTU; - uint64_t trustedPathId = 0; - RR->topology->getOutboundPathInfo(viaPath->address(),mtu,trustedPathId); - - unsigned int chunkSize = std::min(packet.size(),mtu); - packet.setFragmented(chunkSize < packet.size()); - - peer->recordOutgoingPacket(viaPath, packet.packetId(), packet.payloadLength(), packet.verb(), now); - - if (trustedPathId) { - packet.setTrusted(trustedPathId); - } else { - packet.armor(peer->key(),encrypt); - } - - if (viaPath->send(RR,tPtr,packet.data(),chunkSize,now)) { - if (chunkSize < packet.size()) { - // Too big for one packet, fragment the rest - unsigned int fragStart = chunkSize; - unsigned int remaining = packet.size() - chunkSize; - unsigned int fragsRemaining = (remaining / (mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH)); - if ((fragsRemaining * (mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH)) < remaining) - ++fragsRemaining; - const unsigned int totalFragments = fragsRemaining + 1; - - for(unsigned int fno=1;fnosend(RR,tPtr,frag.data(),frag.size(),now); - fragStart += chunkSize; - remaining -= chunkSize; - } + // If sending on all paths, set viaPath to first path + int nextPathIdx = 0; + std::vector> paths = peer->getAllPaths(now); + if (RR->node->getMultipathMode() == ZT_MULTIPATH_BROADCAST) { + if (paths.size()) { + viaPath = paths[nextPathIdx++]; } } + while (viaPath) { + unsigned int mtu = ZT_DEFAULT_PHYSMTU; + uint64_t trustedPathId = 0; + RR->topology->getOutboundPathInfo(viaPath->address(),mtu,trustedPathId); + unsigned int chunkSize = std::min(packet.size(),mtu); + packet.setFragmented(chunkSize < packet.size()); + peer->recordOutgoingPacket(viaPath, packet.packetId(), packet.payloadLength(), packet.verb(), now); + + if (trustedPathId) { + packet.setTrusted(trustedPathId); + } else { + packet.armor(peer->key(),encrypt); + } + + if (viaPath->send(RR,tPtr,packet.data(),chunkSize,now)) { + if (chunkSize < packet.size()) { + // Too big for one packet, fragment the rest + unsigned int fragStart = chunkSize; + unsigned int remaining = packet.size() - chunkSize; + unsigned int fragsRemaining = (remaining / (mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH)); + if ((fragsRemaining * (mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH)) < remaining) + ++fragsRemaining; + const unsigned int totalFragments = fragsRemaining + 1; + + for(unsigned int fno=1;fnosend(RR,tPtr,frag.data(),frag.size(),now); + fragStart += chunkSize; + remaining -= chunkSize; + } + } + } + viaPath.zero(); + if (RR->node->getMultipathMode() == ZT_MULTIPATH_BROADCAST) { + if (paths.size() > nextPathIdx) { + viaPath = paths[nextPathIdx++]; + } + } + } return true; } diff --git a/node/Switch.hpp b/node/Switch.hpp index a531b2686..388e1ccf1 100644 --- a/node/Switch.hpp +++ b/node/Switch.hpp @@ -131,7 +131,7 @@ public: * @param encrypt Encrypt packet payload? (always true except for HELLO) * @param qosBucket Which bucket the rule-system determined this packet should fall into */ - void aqm_enqueue(void *tPtr, const SharedPtr &network, Packet &packet,bool encrypt,int qosBucket); + void aqm_enqueue(void *tPtr, const SharedPtr &network, Packet &packet,bool encrypt,int qosBucket,int64_t flowId = -1); /** * Performs a single AQM cycle and dequeues and transmits all eligible packets on all networks @@ -177,7 +177,7 @@ public: * @param packet Packet to send (buffer may be modified) * @param encrypt Encrypt packet payload? (always true except for HELLO) */ - void send(void *tPtr,Packet &packet,bool encrypt); + void send(void *tPtr,Packet &packet,bool encrypt,int64_t flowId = -1); /** * Request WHOIS on a given address @@ -212,7 +212,7 @@ public: private: bool _shouldUnite(const int64_t now,const Address &source,const Address &destination); - bool _trySend(void *tPtr,Packet &packet,bool encrypt); // packet is modified if return is true + bool _trySend(void *tPtr,Packet &packet,bool encrypt,int64_t flowId = -1); // packet is modified if return is true const RuntimeEnvironment *const RR; int64_t _lastBeaconResponse; @@ -261,16 +261,18 @@ private: struct TXQueueEntry { TXQueueEntry() {} - TXQueueEntry(Address d,uint64_t ct,const Packet &p,bool enc) : + TXQueueEntry(Address d,uint64_t ct,const Packet &p,bool enc,int64_t fid) : dest(d), creationTime(ct), packet(p), - encrypt(enc) {} + encrypt(enc), + flowId(fid) {} Address dest; uint64_t creationTime; Packet packet; // unencrypted/unMAC'd packet -- this is done at send time bool encrypt; + int64_t flowId; }; std::list< TXQueueEntry > _txQueue; Mutex _txQueue_m; diff --git a/node/Trace.cpp b/node/Trace.cpp index b7c002719..e38aaa41e 100644 --- a/node/Trace.cpp +++ b/node/Trace.cpp @@ -109,10 +109,10 @@ void Trace::peerConfirmingUnknownPath(void *const tPtr,const uint64_t networkId, void Trace::peerLinkNowAggregate(void *const tPtr,Peer &peer) { - if ((RR->node->getMultipathMode() == ZT_MULTIPATH_RANDOM)) { + if ((RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_RANDOM)) { ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is now a randomly-distributed aggregate link",peer.address().toInt()); } - if ((RR->node->getMultipathMode() == ZT_MULTIPATH_PROPORTIONALLY_BALANCED)) { + if ((RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_DYNAMIC_OPAQUE)) { ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is now a proportionally-balanced aggregate link",peer.address().toInt()); } } From 963113b86dc0c1c214038333fd35ea0e014fafc7 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 20 Aug 2019 10:38:18 -0700 Subject: [PATCH 013/362] Minor adjustment to how _allowTcpFallbackRelay is disabled when _multipathMode is set --- service/OneService.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/service/OneService.cpp b/service/OneService.cpp index 93e97bdef..0d1dd337b 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -1572,18 +1572,15 @@ public: json &settings = lc["settings"]; _primaryPort = (unsigned int)OSUtils::jsonInt(settings["primaryPort"],(uint64_t)_primaryPort) & 0xffff; - _allowTcpFallbackRelay = OSUtils::jsonBool(settings["allowTcpFallbackRelay"],true); + _multipathMode = (unsigned int)OSUtils::jsonInt(settings["multipathMode"],0); + // multipathMode cannot be used with allowTcpFallbackRelay + _allowTcpFallbackRelay = OSUtils::jsonBool(settings["allowTcpFallbackRelay"],true) && !_multipathMode; _allowSecondaryPort = OSUtils::jsonBool(settings["allowSecondaryPort"],true); _secondaryPort = (unsigned int)OSUtils::jsonInt(settings["secondaryPort"],0); _tertiaryPort = (unsigned int)OSUtils::jsonInt(settings["tertiaryPort"],0); if (_secondaryPort != 0 || _tertiaryPort != 0) { fprintf(stderr,"WARNING: using manually-specified ports. This can cause NAT issues." ZT_EOL_S); } - _multipathMode = (unsigned int)OSUtils::jsonInt(settings["multipathMode"],0); - if (_multipathMode != 0 && _allowTcpFallbackRelay) { - fprintf(stderr,"WARNING: multipathMode cannot be used with allowTcpFallbackRelay. Disabling allowTcpFallbackRelay" ZT_EOL_S); - _allowTcpFallbackRelay = false; - } _portMappingEnabled = OSUtils::jsonBool(settings["portMappingEnabled"],true); #ifndef ZT_SDK From b0a91c018727f20789ac10b70b766f7b6041feb5 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 20 Aug 2019 16:19:20 -0700 Subject: [PATCH 014/362] Partial implementation of ZT_MULTIPATH_ACTIVE_BACKUP --- node/Constants.hpp | 18 +++++++++++ node/Peer.cpp | 75 ++++++++++++++++++++++++++++++++++++++-------- node/Peer.hpp | 2 ++ 3 files changed, 83 insertions(+), 12 deletions(-) diff --git a/node/Constants.hpp b/node/Constants.hpp index 7f962851a..278c705d8 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -357,16 +357,29 @@ /** * How much each factor contributes to the "stability" score of a path */ + +#if 0 +#define ZT_PATH_CONTRIB_PDV (1.5 / 3.0) +#define ZT_PATH_CONTRIB_LATENCY (0.0 / 3.0) +#define ZT_PATH_CONTRIB_THROUGHPUT_DISTURBANCE (1.5 / 3.0) +#else #define ZT_PATH_CONTRIB_PDV (1.0 / 3.0) #define ZT_PATH_CONTRIB_LATENCY (1.0 / 3.0) #define ZT_PATH_CONTRIB_THROUGHPUT_DISTURBANCE (1.0 / 3.0) +#endif /** * How much each factor contributes to the "quality" score of a path */ +#if 0 +#define ZT_PATH_CONTRIB_STABILITY (2.00 / 3.0) +#define ZT_PATH_CONTRIB_THROUGHPUT (0.50 / 3.0) +#define ZT_PATH_CONTRIB_SCOPE (0.50 / 3.0) +#else #define ZT_PATH_CONTRIB_STABILITY (0.75 / 3.0) #define ZT_PATH_CONTRIB_THROUGHPUT (1.50 / 3.0) #define ZT_PATH_CONTRIB_SCOPE (0.75 / 3.0) +#endif /** * How often a QoS packet is sent @@ -475,6 +488,11 @@ */ #define ZT_MULTIPATH_PEER_PING_PERIOD (ZT_PEER_PING_PERIOD / 10) +/** + * How long before we consider a path to be dead in rapid fail-over scenarios + */ +#define ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD 1000 + /** * Paths are considered expired if they have not sent us a real packet in this long */ diff --git a/node/Peer.cpp b/node/Peer.cpp index d1ef9ecf6..7e96b5f06 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -347,7 +347,7 @@ void Peer::computeAggregateAllocation(int64_t now) + (fmaxf(1.0f, relThroughput[i]) * (float)ZT_PATH_CONTRIB_THROUGHPUT) + relScope * (float)ZT_PATH_CONTRIB_SCOPE; relQuality *= age_contrib; - // Arbitrary cutoffs + // Clamp values relQuality = relQuality > (1.00f / 100.0f) ? relQuality : 0.0f; relQuality = relQuality < (99.0f / 100.0f) ? relQuality : 1.0f; totalRelativeQuality += relQuality; @@ -357,7 +357,6 @@ void Peer::computeAggregateAllocation(int64_t now) // Convert set of relative performances into an allocation set for(uint16_t i=0;inode->getMultipathMode() == ZT_MULTIPATH_BALANCE_RANDOM) { _paths[i].p->updateComponentAllocationOfAggregateLink(((float)_pathChoiceHist.countValue(i) / (float)_pathChoiceHist.count()) * 255); } @@ -420,10 +419,10 @@ int Peer::aggregateLinkLogicalPathCount() return pathCount; } -std::vector> Peer::getAllPaths(int64_t now) +std::vector > Peer::getAllPaths(int64_t now) { Mutex::Lock _l(_virtual_paths_m); // FIXME: TX can now lock RX - std::vector> paths; + std::vector > paths; for (int i=0; i<_virtualPaths.size(); i++) { if (_virtualPaths[i]->p) { paths.push_back(_virtualPaths[i]->p); @@ -436,6 +435,8 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired, int64 { Mutex::Lock _l(_paths_m); SharedPtr selectedPath; + char curPathStr[128]; + char newPathStr[128]; unsigned int bestPath = ZT_MAX_PEER_NETWORK_PATHS; /** @@ -511,14 +512,66 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired, int64 * All traffic is sent on all paths. */ if (RR->node->getMultipathMode() == ZT_MULTIPATH_BROADCAST) { - // Not handled here. Handled in Switch.cpp + // Not handled here. Handled in Switch::_trySend() } /** * Only one link is active. Fail-over is immediate. */ if (RR->node->getMultipathMode() == ZT_MULTIPATH_ACTIVE_BACKUP) { - // fprintf(stderr, "ZT_MULTIPATH_ACTIVE_BACKUP\n"); + bool bFoundHotPath = false; + if (!_activeBackupPath) { + /* Select the fist path that appears to still be active. + * This will eventually be user-configurable */ + for (int i=0; ilastIn()) < ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD) { + bFoundHotPath = true; + _activeBackupPath = _paths[i].p; + _activeBackupPath->address().toString(curPathStr); + fprintf(stderr, "selected %s as the primary active-backup path to %llx\n", + curPathStr, this->_id.address().toInt()); + } + } + } + if (!_activeBackupPath) { + return SharedPtr(); + } + if (!bFoundHotPath) { + _activeBackupPath->address().toString(curPathStr); + fprintf(stderr, "no hot paths available to to use as active-backup primary to %llx, selected %s anyway\n", + this->_id.address().toInt(), curPathStr); + } + } + else { + if ((now - _activeBackupPath->lastIn()) > ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD) { + _activeBackupPath->address().toString(curPathStr); + /* Fail-over to the fist path that appears to still be active. + * This will eventually be user-configurable */ + for (int i=0; ilastIn()) < ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD) { + bFoundHotPath = true; + _activeBackupPath->address().toString(curPathStr); // Record path string for later debug trace + _activeBackupPath = _paths[i].p; + _activeBackupPath->address().toString(newPathStr); + } + } + } + if (bFoundHotPath) { + fprintf(stderr, "primary active-backup path %s to %llx appears to be dead, switched to path %s\n", + curPathStr, this->_id.address().toInt(), newPathStr); + } + } + } + return _activeBackupPath; } /** @@ -553,27 +606,25 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired, int64 */ if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_XOR_FLOW) { // fprintf(stderr, "ZT_MULTIPATH_BALANCE_XOR_FLOW (%llx) \n", flowId); - char pathStr[128]; struct Flow *currFlow = NULL; if (_flows.count(flowId)) { currFlow = _flows[flowId]; if (!currFlow->assignedPath) { int idx = abs((int)(currFlow->flowId % (_virtualPaths.size()-1))); currFlow->assignedPath = _virtualPaths[idx]; - _virtualPaths[idx]->p->address().toString(pathStr); + _virtualPaths[idx]->p->address().toString(curPathStr); fprintf(stderr, "assigning flow %llx between this node and peer %llx to path %s at index %d\n", - currFlow->flowId, this->_id.address().toInt(), pathStr, idx); + currFlow->flowId, this->_id.address().toInt(), curPathStr, idx); } else { if (!currFlow->assignedPath->p->alive(now)) { - char newPathStr[128]; - currFlow->assignedPath->p->address().toString(pathStr); + currFlow->assignedPath->p->address().toString(curPathStr); // Re-assign int idx = abs((int)(currFlow->flowId % (_virtualPaths.size()-1))); currFlow->assignedPath = _virtualPaths[idx]; _virtualPaths[idx]->p->address().toString(newPathStr); fprintf(stderr, "path %s assigned to flow %llx between this node and %llx appears to be dead, reassigning to path %s\n", - pathStr, currFlow->flowId, this->_id.address().toInt(), newPathStr); + curPathStr, currFlow->flowId, this->_id.address().toInt(), newPathStr); } } return currFlow->assignedPath->p; diff --git a/node/Peer.hpp b/node/Peer.hpp index 7633ad7d5..84d7d43a2 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -725,6 +725,8 @@ private: std::map _flows; int16_t _roundRobinPathAssignmentIdx; + + SharedPtr _activeBackupPath; }; } // namespace ZeroTier From 5453cab22b26b4caa38457dadf64e5f53920cb2d Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 20 Aug 2019 18:50:38 -0700 Subject: [PATCH 015/362] Added flow-awareness check for policies, more work on ZT_MULTIPATH_ACTIVE_BACKUP --- node/Constants.hpp | 2 +- node/Peer.cpp | 106 ++++++++++++++++++++++++++---------------- node/Peer.hpp | 1 + node/Switch.cpp | 113 +++++++++++++++++++++++++-------------------- node/Switch.hpp | 5 ++ 5 files changed, 134 insertions(+), 93 deletions(-) diff --git a/node/Constants.hpp b/node/Constants.hpp index 278c705d8..ee656aaec 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -491,7 +491,7 @@ /** * How long before we consider a path to be dead in rapid fail-over scenarios */ -#define ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD 1000 +#define ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD 250 /** * Paths are considered expired if they have not sent us a real packet in this long diff --git a/node/Peer.cpp b/node/Peer.cpp index 7e96b5f06..622095e29 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -77,7 +77,8 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident _lastAggregateStatsReport(0), _lastAggregateAllocation(0), _virtualPathCount(0), - _roundRobinPathAssignmentIdx(0) + _roundRobinPathAssignmentIdx(0), + _pathAssignmentIdx(0) { if (!myIdentity.agree(peerIdentity,_key,ZT_PEER_SECRET_KEY_LENGTH)) throw ZT_EXCEPTION_INVALID_ARGUMENT; @@ -468,16 +469,18 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired, int64 _paths[i].p->processBackgroundPathMeasurements(now); } } - // Detect new flows and update existing records - if (_flows.count(flowId)) { - _flows[flowId]->lastSend = now; - } - else { - fprintf(stderr, "new flow %llx detected between this node and %llx (%lu active flow(s))\n", - flowId, this->_id.address().toInt(), (_flows.size()+1)); - struct Flow *newFlow = new Flow(flowId, now); - _flows[flowId] = newFlow; - newFlow->assignedPath = nullptr; + if (RR->sw->isFlowAware()) { + // Detect new flows and update existing records + if (_flows.count(flowId)) { + _flows[flowId]->lastSend = now; + } + else { + fprintf(stderr, "new flow %llx detected between this node and %llx (%lu active flow(s))\n", + flowId, this->_id.address().toInt(), (_flows.size()+1)); + struct Flow *newFlow = new Flow(flowId, now); + _flows[flowId] = newFlow; + newFlow->assignedPath = nullptr; + } } // Construct set of virtual paths if needed if (!_virtualPaths.size()) { @@ -532,45 +535,64 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired, int64 if ((now - _paths[i].p->lastIn()) < ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD) { bFoundHotPath = true; _activeBackupPath = _paths[i].p; + _pathAssignmentIdx = i; _activeBackupPath->address().toString(curPathStr); - fprintf(stderr, "selected %s as the primary active-backup path to %llx\n", - curPathStr, this->_id.address().toInt()); + fprintf(stderr, "selected %s as the primary active-backup path to %llx (idx=%d)\n", + curPathStr, this->_id.address().toInt(), _pathAssignmentIdx); + break; } } } - if (!_activeBackupPath) { - return SharedPtr(); - } - if (!bFoundHotPath) { - _activeBackupPath->address().toString(curPathStr); - fprintf(stderr, "no hot paths available to to use as active-backup primary to %llx, selected %s anyway\n", - this->_id.address().toInt(), curPathStr); - } } else { + char what[128]; if ((now - _activeBackupPath->lastIn()) > ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD) { - _activeBackupPath->address().toString(curPathStr); - /* Fail-over to the fist path that appears to still be active. - * This will eventually be user-configurable */ - for (int i=0; iaddress().toString(curPathStr); // Record path string for later debug trace + int16_t previousIdx = _pathAssignmentIdx; + SharedPtr nextAlternativePath; + // Search for a hot path, at the same time find the next path in + // a RR sequence that seems viable to use as an alternative + int searchCount = 0; + while (searchCount < ZT_MAX_PEER_NETWORK_PATHS) { + _pathAssignmentIdx++; + if (_pathAssignmentIdx == ZT_MAX_PEER_NETWORK_PATHS) { + _pathAssignmentIdx = 0; + } + searchCount++; + if (_paths[_pathAssignmentIdx].p) { + _paths[_pathAssignmentIdx].p->address().toString(what); + if (_activeBackupPath.ptr() == _paths[_pathAssignmentIdx].p.ptr()) { continue; } - if ((now - _paths[i].p->lastIn()) < ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD) { + if (!nextAlternativePath) { // Record the first viable alternative in the RR sequence + nextAlternativePath = _paths[_pathAssignmentIdx].p; + } + if ((now - _paths[_pathAssignmentIdx].p->lastIn()) < ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD) { bFoundHotPath = true; - _activeBackupPath->address().toString(curPathStr); // Record path string for later debug trace - _activeBackupPath = _paths[i].p; + _activeBackupPath = _paths[_pathAssignmentIdx].p; _activeBackupPath->address().toString(newPathStr); + fprintf(stderr, "primary active-backup path %s to %llx appears to be dead, switched to %s\n", + curPathStr, this->_id.address().toInt(), newPathStr); + break; } } } - if (bFoundHotPath) { - fprintf(stderr, "primary active-backup path %s to %llx appears to be dead, switched to path %s\n", - curPathStr, this->_id.address().toInt(), newPathStr); + if (!bFoundHotPath) { + if (nextAlternativePath) { + _activeBackupPath = nextAlternativePath; + _activeBackupPath->address().toString(curPathStr); + //fprintf(stderr, "no hot paths found to use as active-backup primary to %llx, using next best: %s\n", + // this->_id.address().toInt(), curPathStr); + } + else { + // No change + } } } } + if (!_activeBackupPath) { + return SharedPtr(); + } return _activeBackupPath; } @@ -866,14 +888,16 @@ inline void Peer::processBackgroundPeerTasks(const int64_t now) } // Remove old flows - std::map::iterator it = _flows.begin(); - while (it != _flows.end()) { - if ((now - it->second->lastSend) > ZT_MULTIPATH_FLOW_EXPIRATION) { - fprintf(stderr, "forgetting flow %llx between this node and %llx (%lu active flow(s))\n", - it->first, this->_id.address().toInt(), _flows.size()); - it = _flows.erase(it); - } else { - it++; + if (RR->sw->isFlowAware()) { + std::map::iterator it = _flows.begin(); + while (it != _flows.end()) { + if ((now - it->second->lastSend) > ZT_MULTIPATH_FLOW_EXPIRATION) { + fprintf(stderr, "forgetting flow %llx between this node and %llx (%lu active flow(s))\n", + it->first, this->_id.address().toInt(), _flows.size()); + it = _flows.erase(it); + } else { + it++; + } } } } diff --git a/node/Peer.hpp b/node/Peer.hpp index 84d7d43a2..dddd8fc01 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -727,6 +727,7 @@ private: int16_t _roundRobinPathAssignmentIdx; SharedPtr _activeBackupPath; + int16_t _pathAssignmentIdx; }; } // namespace ZeroTier diff --git a/node/Switch.cpp b/node/Switch.cpp index c2251f23d..51f23f674 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -284,6 +284,14 @@ static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsig return false; // overflow == invalid } +bool Switch::isFlowAware() +{ + int mode = RR->node->getMultipathMode(); + return (( mode == ZT_MULTIPATH_BALANCE_RR_FLOW) + || (mode == ZT_MULTIPATH_BALANCE_XOR_FLOW) + || (mode == ZT_MULTIPATH_BALANCE_DYNAMIC_FLOW)); +} + void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) { if (!network->hasConfig()) @@ -309,61 +317,64 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const * preferred virtual path and will be sent out according to what the multipath logic * deems appropriate. An example of this would be an ICMP packet. */ + int64_t flowId = -1; - if (etherType == ZT_ETHERTYPE_IPV4 && (len >= 20)) { - uint16_t srcPort = 0; - uint16_t dstPort = 0; - int8_t proto = (reinterpret_cast(data)[9]); - const unsigned int headerLen = 4 * (reinterpret_cast(data)[0] & 0xf); - switch(proto) { - case 0x01: // ICMP - flowId = 0x01; - break; - // All these start with 16-bit source and destination port in that order - case 0x06: // TCP - case 0x11: // UDP - case 0x84: // SCTP - case 0x88: // UDPLite - if (len > (headerLen + 4)) { - unsigned int pos = headerLen + 0; - srcPort = (reinterpret_cast(data)[pos++]) << 8; - srcPort |= (reinterpret_cast(data)[pos]); - pos++; - dstPort = (reinterpret_cast(data)[pos++]) << 8; - dstPort |= (reinterpret_cast(data)[pos]); - flowId = ((int64_t)srcPort << 48) | ((int64_t)dstPort << 32) | proto; - } - break; + if (isFlowAware()) { + if (etherType == ZT_ETHERTYPE_IPV4 && (len >= 20)) { + uint16_t srcPort = 0; + uint16_t dstPort = 0; + int8_t proto = (reinterpret_cast(data)[9]); + const unsigned int headerLen = 4 * (reinterpret_cast(data)[0] & 0xf); + switch(proto) { + case 0x01: // ICMP + flowId = 0x01; + break; + // All these start with 16-bit source and destination port in that order + case 0x06: // TCP + case 0x11: // UDP + case 0x84: // SCTP + case 0x88: // UDPLite + if (len > (headerLen + 4)) { + unsigned int pos = headerLen + 0; + srcPort = (reinterpret_cast(data)[pos++]) << 8; + srcPort |= (reinterpret_cast(data)[pos]); + pos++; + dstPort = (reinterpret_cast(data)[pos++]) << 8; + dstPort |= (reinterpret_cast(data)[pos]); + flowId = ((int64_t)srcPort << 48) | ((int64_t)dstPort << 32) | proto; + } + break; + } } - } - if (etherType == ZT_ETHERTYPE_IPV6 && (len >= 40)) { - uint16_t srcPort = 0; - uint16_t dstPort = 0; - unsigned int pos; - unsigned int proto; - _ipv6GetPayload((const uint8_t *)data, len, pos, proto); - switch(proto) { - case 0x3A: // ICMPv6 - flowId = 0x3A; - break; - // All these start with 16-bit source and destination port in that order - case 0x06: // TCP - case 0x11: // UDP - case 0x84: // SCTP - case 0x88: // UDPLite - if (len > (pos + 4)) { - srcPort = (reinterpret_cast(data)[pos++]) << 8; - srcPort |= (reinterpret_cast(data)[pos]); - pos++; - dstPort = (reinterpret_cast(data)[pos++]) << 8; - dstPort |= (reinterpret_cast(data)[pos]); - flowId = ((int64_t)srcPort << 48) | ((int64_t)dstPort << 32) | proto; - } - break; - default: - break; + if (etherType == ZT_ETHERTYPE_IPV6 && (len >= 40)) { + uint16_t srcPort = 0; + uint16_t dstPort = 0; + unsigned int pos; + unsigned int proto; + _ipv6GetPayload((const uint8_t *)data, len, pos, proto); + switch(proto) { + case 0x3A: // ICMPv6 + flowId = 0x3A; + break; + // All these start with 16-bit source and destination port in that order + case 0x06: // TCP + case 0x11: // UDP + case 0x84: // SCTP + case 0x88: // UDPLite + if (len > (pos + 4)) { + srcPort = (reinterpret_cast(data)[pos++]) << 8; + srcPort |= (reinterpret_cast(data)[pos]); + pos++; + dstPort = (reinterpret_cast(data)[pos++]) << 8; + dstPort |= (reinterpret_cast(data)[pos]); + flowId = ((int64_t)srcPort << 48) | ((int64_t)dstPort << 32) | proto; + } + break; + default: + break; + } } } diff --git a/node/Switch.hpp b/node/Switch.hpp index 388e1ccf1..666ee0531 100644 --- a/node/Switch.hpp +++ b/node/Switch.hpp @@ -91,6 +91,11 @@ public: */ void onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddress &fromAddr,const void *data,unsigned int len); + /** + * Returns whether our bonding or balancing policy is aware of flows. + */ + bool isFlowAware(); + /** * Called when a packet comes from a local Ethernet tap * From afca5c25369aab1a3cb1144229ada1e9f5609030 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 20 Aug 2019 23:28:59 -0700 Subject: [PATCH 016/362] Partial implementation of ZT_MULTIPATH_BALANCE_RR_OPAQUE --- node/Peer.cpp | 79 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/node/Peer.cpp b/node/Peer.cpp index 622095e29..f9e452c12 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -491,26 +491,6 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired, int64 return SharedPtr(); } - /** - * Traffic is randomly distributed among all active paths. - */ - int numAlivePaths = 0; - int numStalePaths = 0; - if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_RANDOM) { - int sz = _virtualPaths.size(); - if (sz) { - int idx = _freeRandomByte % sz; - _pathChoiceHist.push(idx); - char pathStr[128]; - _virtualPaths[idx]->p->address().toString(pathStr); - fprintf(stderr, "sending out: (%llx), idx=%d: path=%s, localSocket=%lld\n", - this->_id.address().toInt(), idx, pathStr, _virtualPaths[idx]->localSocket); - return _virtualPaths[idx]->p; - } - // This call is algorithmically inert but gives us a value to show in the status output - computeAggregateAllocation(now); - } - /** * All traffic is sent on all paths. */ @@ -596,23 +576,64 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired, int64 return _activeBackupPath; } + /** + * Traffic is randomly distributed among all active paths. + */ + if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_RANDOM) { + int sz = _virtualPaths.size(); + if (sz) { + int idx = _freeRandomByte % sz; + _pathChoiceHist.push(idx); + _virtualPaths[idx]->p->address().toString(curPathStr); + fprintf(stderr, "sending out: (%llx), idx=%d: path=%s, localSocket=%lld\n", + this->_id.address().toInt(), idx, curPathStr, _virtualPaths[idx]->localSocket); + return _virtualPaths[idx]->p; + } + // This call is algorithmically inert but gives us a value to show in the status output + computeAggregateAllocation(now); + } + /** * Packets are striped across all available paths. */ if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_RR_OPAQUE) { - // fprintf(stderr, "ZT_MULTIPATH_BALANCE_RR_OPAQUE\n"); int16_t previousIdx = _roundRobinPathAssignmentIdx; - if (_roundRobinPathAssignmentIdx < (_virtualPaths.size()-1)) { - _roundRobinPathAssignmentIdx++; + int cycleCount = 0; + int minLastIn = 0; + int bestAlternativeIdx = -1; + while (cycleCount < ZT_MAX_PEER_NETWORK_PATHS) { + if (_roundRobinPathAssignmentIdx < (_virtualPaths.size()-1)) { + _roundRobinPathAssignmentIdx++; + } + else { + _roundRobinPathAssignmentIdx = 0; + } + cycleCount++; + if (_virtualPaths[_roundRobinPathAssignmentIdx]->p) { + uint64_t lastIn = _virtualPaths[_roundRobinPathAssignmentIdx]->p->lastIn(); + if (bestAlternativeIdx == -1) { + minLastIn = lastIn; // Initialization + bestAlternativeIdx = 0; + } + if (lastIn < minLastIn) { + minLastIn = lastIn; + bestAlternativeIdx = _roundRobinPathAssignmentIdx; + } + if ((now - lastIn) < 5000) { + selectedPath = _virtualPaths[_roundRobinPathAssignmentIdx]->p; + } + } } - else { - _roundRobinPathAssignmentIdx = 0; + // If we can't find an appropriate path, try the most recently active one + if (!selectedPath) { + _roundRobinPathAssignmentIdx = bestAlternativeIdx; + selectedPath = _virtualPaths[bestAlternativeIdx]->p; + selectedPath->address().toString(curPathStr); + fprintf(stderr, "could not find good path, settling for next best %s\n",curPathStr); } - selectedPath = _virtualPaths[previousIdx]->p; - char pathStr[128]; - selectedPath->address().toString(pathStr); + selectedPath->address().toString(curPathStr); fprintf(stderr, "sending packet out on path %s at index %d\n", - pathStr, previousIdx); + curPathStr, _roundRobinPathAssignmentIdx); return selectedPath; } From de127056c2e7a8de5beb23085d3f18ef962840bf Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Mon, 16 Sep 2019 20:13:20 -0700 Subject: [PATCH 017/362] Add notarization to "make official". --- make-mac.mk | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/make-mac.mk b/make-mac.mk index c71b3f778..1fe2d7a4c 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -8,6 +8,8 @@ CODESIGN=echo PRODUCTSIGN=echo CODESIGN_APP_CERT= CODESIGN_INSTALLER_CERT= +NOTARIZE=echo +NOTARIZE_USER_ID=null ZT_BUILD_PLATFORM=3 ZT_BUILD_ARCHITECTURE=2 @@ -34,6 +36,8 @@ ifeq ($(ZT_OFFICIAL_RELEASE),1) PRODUCTSIGN=productsign CODESIGN_APP_CERT="Developer ID Application: ZeroTier, Inc (8ZD9JUCZ4V)" CODESIGN_INSTALLER_CERT="Developer ID Installer: ZeroTier, Inc (8ZD9JUCZ4V)" + NOTARIZE=xcrun altool + NOTARIZE_USER_ID="adam.ierymenko@gmail.com" else DEFS+=-DZT_SOFTWARE_UPDATE_DEFAULT="\"download\"" endif @@ -76,7 +80,7 @@ ifeq ($(ZT_VAULT_SUPPORT),1) LIBS+=-lcurl endif -CXXFLAGS=$(CFLAGS) -std=c++11 -stdlib=libc++ +CXXFLAGS=$(CFLAGS) -std=c++11 -stdlib=libc++ all: one macui @@ -131,6 +135,8 @@ mac-dist-pkg: FORCE if [ -f "ZeroTier One Signed.pkg" ]; then mv -f "ZeroTier One Signed.pkg" "ZeroTier One.pkg"; fi rm -f zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_* cat ext/installfiles/mac-update/updater.tmpl.sh "ZeroTier One.pkg" >zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_$(ZT_VERSION_MAJOR).$(ZT_VERSION_MINOR).$(ZT_VERSION_REV)_$(ZT_VERSION_BUILD).exe + $(NOTARIZE) -t osx -f "ZeroTier One.pkg" --primary-bundle-id --output-format xml --notarize-app -u $(NOTARIZE_USER_ID) + echo '*** When Apple notifies that the app is notarized, run: xcrun stapler staple "ZeroTier One.pkg"' # For ZeroTier, Inc. to build official signed packages official: FORCE From bad95d7c675f04aa10773d46901d65342382c03c Mon Sep 17 00:00:00 2001 From: Travis LaDuke Date: Tue, 15 Oct 2019 13:13:08 -0700 Subject: [PATCH 018/362] Update cli help to say instead of Closes #1054 --- one.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/one.cpp b/one.cpp index 06d56e7ae..99a3a575b 100644 --- a/one.cpp +++ b/one.cpp @@ -115,10 +115,10 @@ static void cliPrintHelp(const char *pn,FILE *out) fprintf(out," listpeers - List all peers" ZT_EOL_S); fprintf(out," peers - List all peers (prettier)" ZT_EOL_S); fprintf(out," listnetworks - List all networks" ZT_EOL_S); - fprintf(out," join - Join a network" ZT_EOL_S); - fprintf(out," leave - Leave a network" ZT_EOL_S); - fprintf(out," set - Set a network setting" ZT_EOL_S); - fprintf(out," get - Get a network setting" ZT_EOL_S); + fprintf(out," join - Join a network" ZT_EOL_S); + fprintf(out," leave - Leave a network" ZT_EOL_S); + fprintf(out," set - Set a network setting" ZT_EOL_S); + fprintf(out," get - Get a network setting" ZT_EOL_S); fprintf(out," listmoons - List moons (federated root sets)" ZT_EOL_S); fprintf(out," orbit - Join a moon via any member root" ZT_EOL_S); fprintf(out," deorbit - Leave a moon" ZT_EOL_S); @@ -126,7 +126,7 @@ static void cliPrintHelp(const char *pn,FILE *out) fprintf(out," Settings to use with [get/set] may include property names from " ZT_EOL_S); fprintf(out," the JSON output of \"zerotier-cli -j listnetworks\". Additionally, " ZT_EOL_S); fprintf(out," (ip, ip4, ip6, ip6plane, and ip6prefix can be used). For instance:" ZT_EOL_S); - fprintf(out," zerotier-cli get ip6plane will return the 6PLANE address" ZT_EOL_S); + fprintf(out," zerotier-cli get ip6plane will return the 6PLANE address" ZT_EOL_S); fprintf(out," assigned to this node." ZT_EOL_S); } From 86abd31659d385b52d329cfcf8b6e203b8c85a46 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 13 Nov 2019 12:46:16 -0800 Subject: [PATCH 019/362] fix log line --- controller/PostgreSQL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 08e06ee98..8f8c2785a 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -760,7 +760,7 @@ void PostgreSQL::networksDbWatcher() fprintf(stderr, "ERROR: %s networksDbWatcher should still be running! Exiting Controller.\n", _myAddressStr.c_str()); exit(8); } - fprintf(stderr, "Exited membersDbWatcher\n"); + fprintf(stderr, "Exited networksDbWatcher\n"); } void PostgreSQL::_networksWatcher_Postgres(PGconn *conn) { From dc19deb6c3f23bb3dec7d4141a57b09369138313 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 13 Nov 2019 12:46:52 -0800 Subject: [PATCH 020/362] . --- .gitignore | 1 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 macui/ZeroTier One.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/.gitignore b/.gitignore index 44b5eb56b..62a6c6a17 100755 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,4 @@ __pycache__ *~ attic/world/*.c25519 attic/world/mkworld +workspace/ diff --git a/macui/ZeroTier One.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macui/ZeroTier One.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/macui/ZeroTier One.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + From 8396f16dd2d26aece025e4a939d81340c4860ba9 Mon Sep 17 00:00:00 2001 From: Dave Cottlehuber Date: Tue, 19 Nov 2019 16:45:06 +0000 Subject: [PATCH 021/362] drop -Werror from DEBUG builds Appease compilers. Thanks @glimberg for the diff. Closes #1086 --- make-bsd.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make-bsd.mk b/make-bsd.mk index 8c7a6ad2e..62a6d73e6 100644 --- a/make-bsd.mk +++ b/make-bsd.mk @@ -13,7 +13,7 @@ ifeq ($(ZT_SANITIZE),1) endif # "make debug" is a shortcut for this ifeq ($(ZT_DEBUG),1) - CFLAGS+=-Wall -Werror -g -pthread $(INCLUDES) $(DEFS) + CFLAGS+=-Wall -g -pthread $(INCLUDES) $(DEFS) LDFLAGS+= STRIP=echo ZT_TRACE=1 From 381f15382c2ec06c77fe2d54a0208010248f935c Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 20 Nov 2019 13:56:26 -0800 Subject: [PATCH 022/362] Convert central-controller docker build to use a builder subcontainer --- ext/central-controller-docker/Dockerfile | 19 +++++++++++++------ make-linux.mk | 4 ++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/ext/central-controller-docker/Dockerfile b/ext/central-controller-docker/Dockerfile index 2fc92e6f9..716390f82 100644 --- a/ext/central-controller-docker/Dockerfile +++ b/ext/central-controller-docker/Dockerfile @@ -1,19 +1,26 @@ # Dockerfile for ZeroTier Central Controllers -FROM centos:7 +FROM centos:7 as builder MAINTAINER Adam Ierymekno , Grant Limberg +ARG git_branch=master + RUN yum update -y RUN yum install -y https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-centos10-10-2.noarch.rpm -RUN yum install -y bash postgresql10 libpqxx-devel - RUN yum -y install epel-release && yum -y update && yum clean all -RUN yum -y install clang jemalloc jemalloc-devel +RUN yum groupinstall -y "Development Tools" +RUN yum install -y bash postgresql10 postgresql10-devel libpqxx-devel glibc-static libstdc++-static clang jemalloc jemalloc-devel +RUN git clone http://git.int.zerotier.com/zerotier/ZeroTierOne.git +RUN if [ "$git_branch" != "master" ]; then cd ZeroTierOne && git checkout -b $git_branch origin/$git_branch; fi +RUN ldconfig +RUN cd ZeroTierOne && make central-controller -ADD zerotier-one /usr/local/bin/zerotier-one +FROM centos:7 + +COPY --from=builder /ZeroTierOne/zerotier-one /usr/local/bin/zerotier-one RUN chmod a+x /usr/local/bin/zerotier-one -ADD docker/main.sh / +ADD ext/central-controller-docker/main.sh / RUN chmod a+x /main.sh ENTRYPOINT /main.sh diff --git a/make-linux.mk b/make-linux.mk index 9fd4f7b95..b31811437 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -334,8 +334,8 @@ docker: FORCE central-controller: FORCE make -j4 LDLIBS="-L/usr/pgsql-10/lib/ -lpq -Lext/librabbitmq/centos_x64/lib/ -lrabbitmq" CXXFLAGS="-I/usr/pgsql-10/include -I./ext/librabbitmq/centos_x64/include -fPIC" DEFS="-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER" ZT_OFFICIAL=1 ZT_USE_X64_ASM_ED25519=1 one -central-controller-docker: central-controller - docker build -t docker.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile . +central-controller-docker: FORCE + docker build -t docker.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=`git name-rev --name-only HEAD` . debug: FORCE make ZT_DEBUG=1 one From e58a0b3efbbd415fa135d16f613127cd4770adfc Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 21 Nov 2019 14:49:24 -0800 Subject: [PATCH 023/362] build central controller docker images on macOS --- make-mac.mk | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/make-mac.mk b/make-mac.mk index 545dabee8..62df1f6fa 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -16,6 +16,9 @@ ZT_VERSION_MINOR=$(shell cat version.h | grep -F VERSION_MINOR | cut -d ' ' -f 3 ZT_VERSION_REV=$(shell cat version.h | grep -F VERSION_REVISION | cut -d ' ' -f 3) ZT_VERSION_BUILD=$(shell cat version.h | grep -F VERSION_BUILD | cut -d ' ' -f 3) +# for central controller builds +TIMESTAMP=$(shell date +"%Y%m%d%H%M") + DEFS+=-DZT_BUILD_PLATFORM=$(ZT_BUILD_PLATFORM) -DZT_BUILD_ARCHITECTURE=$(ZT_BUILD_ARCHITECTURE) include objects.mk @@ -140,6 +143,9 @@ official: FORCE make ZT_OFFICIAL_RELEASE=1 macui make ZT_OFFICIAL_RELEASE=1 mac-dist-pkg +central-controller-docker: FORCE + docker build -t docker.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . + clean: rm -rf MacEthernetTapAgent *.dSYM build-* *.a *.pkg *.dmg *.o node/*.o controller/*.o service/*.o osdep/*.o ext/http-parser/*.o $(CORE_OBJS) $(ONE_OBJS) zerotier-one zerotier-idtool zerotier-selftest zerotier-cli zerotier doc/node_modules macui/build zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_* From 54e25084feb1d5994650d3e104c56e8de48ce3f9 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 21 Nov 2019 14:51:33 -0800 Subject: [PATCH 024/362] re-enable CI on master --- Jenkinsfile | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 88989327e..edb0c69e6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -40,26 +40,26 @@ parallel 'centos7': { // throw err // } // } -// }, 'macOS': { -// node('macOS') { -// try { -// checkout scm +}, 'macOS': { + node('macOS') { + try { + checkout scm -// stage('Build macOS') { -// sh 'make -f make-mac.mk' -// } + stage('Build macOS') { + sh 'make -f make-mac.mk' + } -// stage('Build macOS UI') { -// sh 'cd macui && xcodebuild -target "ZeroTier One" -configuration Debug' -// } -// } -// catch (err) { -// currentBuild.result = "FAILURE" -// mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on macOS (<${env.BUILD_URL}|Open>)" + stage('Build macOS UI') { + sh 'cd macui && xcodebuild -target "ZeroTier One" -configuration Debug' + } + } + catch (err) { + currentBuild.result = "FAILURE" + mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on macOS (<${env.BUILD_URL}|Open>)" -// throw err -// } -// } + throw err + } + } // }, 'windows': { // node('windows') { // try { From d8597884f6a49bbe390eb56a4443a0c63cda4453 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Fri, 22 Nov 2019 11:25:31 -0800 Subject: [PATCH 025/362] jenkins windows --- Jenkinsfile | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index edb0c69e6..2c4c97fb3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -60,25 +60,24 @@ parallel 'centos7': { throw err } } -// }, 'windows': { -// node('windows') { -// try { -// checkout scm +}, 'windows': { + node('windows') { + try { + checkout scm -// stage('Build Windows') { -// bat '''CALL "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat" amd64 -// git clean -dfx -// msbuild windows\\ZeroTierOne.sln -// ''' -// } -// } -// catch (err) { -// currentBuild.result = "FAILURE" -// mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on Windows (<${env.BUILD_URL}|Open>)" + stage('Build Windows') { + bat '''CALL "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars32.bat" x64 +msbuild windows\\ZeroTierOne.sln +''' + } + } + catch (err) { + currentBuild.result = "FAILURE" + mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on Windows (<${env.BUILD_URL}|Open>)" -// throw err -// } -// } + throw err + } + } } mattermostSend color: "#00ff00", message: "${env.JOB_NAME} #${env.BUILD_NUMBER} Complete (<${env.BUILD_URL}|Show More...>)" From 2a7d4fcde52e60a97a50642ed5dc45867fed0943 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 17 Dec 2019 10:49:21 -0800 Subject: [PATCH 026/362] update vs project settings --- windows/ZeroTierOne/ZeroTierOne.vcxproj | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj b/windows/ZeroTierOne/ZeroTierOne.vcxproj index 1cfe8ae0e..a91ed7d1a 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj @@ -220,37 +220,37 @@ {B00A4957-5977-4AC1-9EF4-571DC27EADA2} ZeroTierOne - 10.0.17134.0 + 10.0 Application true - v141 + v142 MultiByte Application true - v141 + v142 MultiByte Application true - v141 + v142 MultiByte Application true - v141 + v142 MultiByte Application false - v141 + v142 true MultiByte Static @@ -258,7 +258,7 @@ Application false - v141 + v142 true MultiByte Static From 7696d450931b203049d722101028c7b3233585b0 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 17 Dec 2019 10:51:01 -0800 Subject: [PATCH 027/362] Fix handling of invalid conf file Fixes GitHub issue #1068 --- windows/WinUI/CentralAPI.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/windows/WinUI/CentralAPI.cs b/windows/WinUI/CentralAPI.cs index b0b4a267a..22bdc697c 100644 --- a/windows/WinUI/CentralAPI.cs +++ b/windows/WinUI/CentralAPI.cs @@ -75,7 +75,15 @@ namespace WinUI { byte[] tmp = File.ReadAllBytes(centralConfigPath); string json = Encoding.UTF8.GetString(tmp).Trim(); - Central = JsonConvert.DeserializeObject(json); + CentralServer ctmp = JsonConvert.DeserializeObject(json); + if (ctmp != null) + { + Central = ctmp; + } + else + { + Central = new CentralServer(); + } } else { @@ -105,7 +113,10 @@ namespace WinUI { string json = JsonConvert.SerializeObject(Central); byte[] tmp = Encoding.UTF8.GetBytes(json); - File.WriteAllBytes(CentralConfigFile(), tmp); + if (tmp != null) + { + File.WriteAllBytes(CentralConfigFile(), tmp); + } } private void UpdateRequestHeaders() From e0518ab176a1c381afe7237b351e527fa49bf91c Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 22 Jan 2020 16:36:26 -0800 Subject: [PATCH 028/362] testing armv5t instead of armv5 flag --- make-linux.mk | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/make-linux.mk b/make-linux.mk index b31811437..532846811 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -260,12 +260,12 @@ endif # ARM32 hell -- use conservative CFLAGS ifeq ($(ZT_ARCHITECTURE),3) ifeq ($(shell if [ -e /usr/bin/dpkg ]; then dpkg --print-architecture; fi),armel) - override CFLAGS+=-march=armv5 -mfloat-abi=soft -msoft-float -mno-unaligned-access -marm - override CXXFLAGS+=-march=armv5 -mfloat-abi=soft -msoft-float -mno-unaligned-access -marm + override CFLAGS+=-march=armv5t -mfloat-abi=soft -msoft-float -mno-unaligned-access -marm + override CXXFLAGS+=-march=armv5t -mfloat-abi=soft -msoft-float -mno-unaligned-access -marm ZT_USE_ARM32_NEON_ASM_CRYPTO=0 else - override CFLAGS+=-march=armv5 -mno-unaligned-access -marm -fexceptions - override CXXFLAGS+=-march=armv5 -mno-unaligned-access -marm -fexceptions + override CFLAGS+=-march=armv5t -mno-unaligned-access -marm -fexceptions + override CXXFLAGS+=-march=armv5t -mno-unaligned-access -marm -fexceptions ZT_USE_ARM32_NEON_ASM_CRYPTO=0 endif endif From 0f17cd47918f8d5dad5dcb531dc80ccc7a9b8605 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 23 Jan 2020 09:41:17 -0800 Subject: [PATCH 029/362] Update to jenkinsfile for multi-platform release builds --- Jenkinsfile | 119 ++++++++++++++++++++-------------------------------- 1 file changed, 45 insertions(+), 74 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2c4c97fb3..796c70367 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,83 +1,54 @@ #!/usr/bin/env groovy +options { + disableConcurrentBuilds() +} -node('master') { - checkout scm +def alpineStaticTask(distro, platform) { + def myNode = { + node ('linux-build') { + checkout scm + def runtime = docker.image("ztbuild/${distro}-${platform}:latest") + runtime.inside { + sh 'make -j8 ZT_STATIC=1 all' + sh "mv zerotier-one zerotier-one-static-${platform}" + archiveArtifacts artifacts: 'zerotier-one-*', fingerprint: true, onlyIfSuccessful: true + } + } + } + return myNode +} + +def getTasks(axisDistro, axisPlatform, task) { + def tasks = [:] + for(int i=0; i< axisDistro.size(); i++) { + def axisDistroValue = axisDistro[i] + for(int j=0; j< axisPlatform.size(); j++) { + def axisPlatformValue = axisPlatform[j] + tasks["${axisDistroValue}/${axisPlatformValue}"] = task(axisDistroValue, axisPlatformValue) + } + } + return tasks +} + +pipeline { + options { + disableConcurrentBuilds() + } - def changelog = getChangeLog currentBuild - - mattermostSend "Building ${env.JOB_NAME} #${env.BUILD_NUMBER} \n Change Log: \n ${changelog}" -} - -parallel 'centos7': { - node('centos7') { - try { - checkout scm - - stage('Build Centos 7') { - sh 'make -f make-linux.mk' + agent none + + stages { + stage ("Static Build") { + steps { + script { + def dist = ["alpine"] + def archs = ["aarch64", "amd64", "i386", "armhf", "armel", "ppc64le", "s390x"] + parallel getTasks(dist, archs, this.&alpineStaticTask) + } } } - catch (err) { - currentBuild.result = "FAILURE" - mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on Centos 7 (<${env.BUILD_URL}|Open>)" - - throw err - } - } -// }, 'android-ndk': { -// node('android-ndk') { -// try { -// checkout scm - -// stage('Build Android NDK') { -// sh "/android/android-ndk-r15b/ndk-build -C $WORKSPACE/java ZT1=${WORKSPACE}" -// } -// } -// catch (err) { -// currentBuild.result = "FAILURE" -// mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on Android NDK (<${env.BUILD_URL}|Open>)" - -// throw err -// } -// } -}, 'macOS': { - node('macOS') { - try { - checkout scm - - stage('Build macOS') { - sh 'make -f make-mac.mk' - } - - stage('Build macOS UI') { - sh 'cd macui && xcodebuild -target "ZeroTier One" -configuration Debug' - } - } - catch (err) { - currentBuild.result = "FAILURE" - mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on macOS (<${env.BUILD_URL}|Open>)" - - throw err - } - } -}, 'windows': { - node('windows') { - try { - checkout scm - - stage('Build Windows') { - bat '''CALL "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars32.bat" x64 -msbuild windows\\ZeroTierOne.sln -''' - } - } - catch (err) { - currentBuild.result = "FAILURE" - mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on Windows (<${env.BUILD_URL}|Open>)" - - throw err - } } } + mattermostSend color: "#00ff00", message: "${env.JOB_NAME} #${env.BUILD_NUMBER} Complete (<${env.BUILD_URL}|Show More...>)" From 5faf0f8ab668f33edde75946ff9d90a4e602b21f Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 23 Jan 2020 09:46:15 -0800 Subject: [PATCH 030/362] whoops --- Jenkinsfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 796c70367..81f7c8156 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,4 @@ #!/usr/bin/env groovy -options { - disableConcurrentBuilds() -} def alpineStaticTask(distro, platform) { def myNode = { From d5467e130e97b8d3f7496f7f7c645096b6ddea4f Mon Sep 17 00:00:00 2001 From: root Date: Fri, 31 Jan 2020 13:13:02 -0800 Subject: [PATCH 031/362] disable lintian because sid/bullseye are sitting there running /bin/sleep 3 endlessly on this step --- make-linux.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make-linux.mk b/make-linux.mk index b31811437..6334f0165 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -390,7 +390,7 @@ uninstall: FORCE # These are just for convenience for building Linux packages debian: FORCE - debuild -I -i -us -uc -nc -b + debuild --no-lintian -I -i -us -uc -nc -b debian-clean: FORCE rm -rf debian/files debian/zerotier-one*.debhelper debian/zerotier-one.substvars debian/*.log debian/zerotier-one debian/.debhelper debian/debhelper-build-stamp From 23d5a972ba433c360d87426129e98f8cf0d78067 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 31 Jan 2020 13:13:02 -0800 Subject: [PATCH 032/362] disable lintian because sid/bullseye are sitting there running /bin/sleep 3 endlessly on this step --- make-linux.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make-linux.mk b/make-linux.mk index 532846811..8b63d7998 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -390,7 +390,7 @@ uninstall: FORCE # These are just for convenience for building Linux packages debian: FORCE - debuild -I -i -us -uc -nc -b + debuild --no-lintian -I -i -us -uc -nc -b debian-clean: FORCE rm -rf debian/files debian/zerotier-one*.debhelper debian/zerotier-one.substvars debian/*.log debian/zerotier-one debian/.debhelper debian/debhelper-build-stamp From f6f4fba90e32dd253570f4562fabd72af2158552 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 3 Feb 2020 09:57:48 -0800 Subject: [PATCH 033/362] Add docker configs for multi arch jenkins builds --- dockerbuild/Dockerfile.alpine | 23 ++++++ dockerbuild/Dockerfile.centos6 | 20 +++++ dockerbuild/Dockerfile.centos6-i386 | 21 +++++ dockerbuild/Dockerfile.centos7 | 25 ++++++ dockerbuild/Dockerfile.centos7-i386 | 22 ++++++ dockerbuild/Dockerfile.centos8 | 25 ++++++ dockerbuild/Dockerfile.clefos-s390x | 20 +++++ dockerbuild/Dockerfile.debian-bullseye | 15 ++++ dockerbuild/Dockerfile.debian-buster | 15 ++++ dockerbuild/Dockerfile.debian-jessie | 15 ++++ dockerbuild/Dockerfile.debian-sid | 15 ++++ dockerbuild/Dockerfile.debian-stretch | 15 ++++ dockerbuild/Dockerfile.debian-wheezy | 23 ++++++ dockerbuild/Dockerfile.ubuntu-bionic | 15 ++++ dockerbuild/Dockerfile.ubuntu-eoan | 15 ++++ dockerbuild/Dockerfile.ubuntu-trusty | 15 ++++ dockerbuild/Dockerfile.ubuntu-xenial | 15 ++++ dockerbuild/Makefile | 104 +++++++++++++++++++++++++ dockerbuild/authorized_keys | 2 + dockerbuild/pipelint.sh | 13 ++++ 20 files changed, 433 insertions(+) create mode 100644 dockerbuild/Dockerfile.alpine create mode 100644 dockerbuild/Dockerfile.centos6 create mode 100644 dockerbuild/Dockerfile.centos6-i386 create mode 100644 dockerbuild/Dockerfile.centos7 create mode 100644 dockerbuild/Dockerfile.centos7-i386 create mode 100644 dockerbuild/Dockerfile.centos8 create mode 100644 dockerbuild/Dockerfile.clefos-s390x create mode 100644 dockerbuild/Dockerfile.debian-bullseye create mode 100644 dockerbuild/Dockerfile.debian-buster create mode 100644 dockerbuild/Dockerfile.debian-jessie create mode 100644 dockerbuild/Dockerfile.debian-sid create mode 100644 dockerbuild/Dockerfile.debian-stretch create mode 100644 dockerbuild/Dockerfile.debian-wheezy create mode 100644 dockerbuild/Dockerfile.ubuntu-bionic create mode 100644 dockerbuild/Dockerfile.ubuntu-eoan create mode 100644 dockerbuild/Dockerfile.ubuntu-trusty create mode 100644 dockerbuild/Dockerfile.ubuntu-xenial create mode 100644 dockerbuild/Makefile create mode 100644 dockerbuild/authorized_keys create mode 100644 dockerbuild/pipelint.sh diff --git a/dockerbuild/Dockerfile.alpine b/dockerbuild/Dockerfile.alpine new file mode 100644 index 000000000..1610ce52e --- /dev/null +++ b/dockerbuild/Dockerfile.alpine @@ -0,0 +1,23 @@ +FROM alpine:3.11.3 + +ARG go_pkg_url + +RUN apk add --update alpine-sdk linux-headers cmake openssh curl + + +RUN adduser -D -s /bin/ash jenkins && \ + passwd -u jenkins && \ + ssh-keygen -A && \ + mkdir /home/jenkins/.ssh && \ + chown -R jenkins:jenkins /home/jenkins + +RUN curl -s $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz + +COPY authorized_keys /home/jenkins/.ssh/authorized_keys +RUN chown -R jenkins:jenkins /home/jenkins/.ssh && \ + chmod 600 /home/jenkins/.ssh/authorized_keys + +EXPOSE 22 +CMD ["/usr/sbin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.centos6 b/dockerbuild/Dockerfile.centos6 new file mode 100644 index 000000000..6b8f023ed --- /dev/null +++ b/dockerbuild/Dockerfile.centos6 @@ -0,0 +1,20 @@ +FROM centos:6 + +ARG go_pkg_url + +RUN yum update -y +RUN yum install -y curl git wget openssh-server sudo make rpmdevtools && yum clean all + +RUN curl -s $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN groupadd -g 1000 jenkins-build && useradd -u 1000 -g 1000 jenkins-build + +RUN echo $'\n\ + export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin\n'\ + >> ~/.bash_profile + +RUN mkdir /rpmbuild && chmod 777 /rpmbuild + +CMD ["/usr/sbin/sshd", "-D"] diff --git a/dockerbuild/Dockerfile.centos6-i386 b/dockerbuild/Dockerfile.centos6-i386 new file mode 100644 index 000000000..c6a47072b --- /dev/null +++ b/dockerbuild/Dockerfile.centos6-i386 @@ -0,0 +1,21 @@ +FROM i386/centos:6 + +ARG go_pkg_url + +RUN echo i386 > /etc/yum/vars/basearch && echo i686 > /etc/yum/vars/arch + +RUN yum install -y curl git wget openssh-server sudo make rpmdevtools && yum clean all + +RUN curl -s $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN groupadd -g 1000 jenkins-build && useradd -u 1000 -g 1000 jenkins-build + +RUN echo $'\n\ + export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin\n'\ + >> ~/.bash_profile + +RUN mkdir /rpmbuild && chmod 777 /rpmbuild + +CMD ["/usr/sbin/sshd", "-D"] diff --git a/dockerbuild/Dockerfile.centos7 b/dockerbuild/Dockerfile.centos7 new file mode 100644 index 000000000..751d02c0c --- /dev/null +++ b/dockerbuild/Dockerfile.centos7 @@ -0,0 +1,25 @@ +FROM centos:7 + +ARG go_pkg_url + +RUN yum install -y epel-release +RUN yum install -y curl git wget openssh-server sudo make development-tools rpmdevtools clang gcc-c++ ruby ruby-devel centos-release-scl devtoolset-8 llvm-toolset-7 && yum clean all + +RUN curl -s $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN wget -qO- "https://cmake.org/files/v3.15/cmake-3.15.1-Linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C /usr/local + +RUN /usr/bin/ssh-keygen -A +RUN useradd jenkins-build + +RUN echo $'\n\ + export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin\n\ + source scl_source enable devtoolset-8 llvm-toolset-7\n'\ + >> ~/.bash_profile + +RUN mkdir /rpmbuild && chmod 777 /rpmbuild + +CMD ["/usr/sbin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.centos7-i386 b/dockerbuild/Dockerfile.centos7-i386 new file mode 100644 index 000000000..f7ee9a0c4 --- /dev/null +++ b/dockerbuild/Dockerfile.centos7-i386 @@ -0,0 +1,22 @@ +FROM centos:7 + +ARG go_pkg_url + +RUN yum install -y curl git wget openssh-server sudo make development-tools rpmdevtools clang gcc-c++ ruby ruby-devel && yum clean all + +RUN curl -s $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN /usr/bin/ssh-keygen -A + +RUN useradd jenkins-build + +RUN echo $'\n\ + export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin\n'\ + >> ~/.bash_profile + +RUN mkdir /rpmbuild && chmod 777 /rpmbuild + +CMD ["/usr/sbin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.centos8 b/dockerbuild/Dockerfile.centos8 new file mode 100644 index 000000000..106ab5b44 --- /dev/null +++ b/dockerbuild/Dockerfile.centos8 @@ -0,0 +1,25 @@ +FROM centos:8 + +ARG go_pkg_url + +RUN yum install -y epel-release +RUN yum install -y curl git wget openssh-server sudo make rpmdevtools clang gcc-c++ ruby ruby-devel && yum clean all + +RUN curl -s $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN wget -qO- "https://cmake.org/files/v3.15/cmake-3.15.1-Linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C /usr/local + +RUN /usr/bin/ssh-keygen -A +RUN useradd jenkins-build + +RUN echo $'\n\ + export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin\n\ + source scl_source enable devtoolset-8 llvm-toolset-7\n'\ + >> ~/.bash_profile + +RUN mkdir /rpmbuild && chmod 777 /rpmbuild + +CMD ["/usr/sbin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.clefos-s390x b/dockerbuild/Dockerfile.clefos-s390x new file mode 100644 index 000000000..135f70abc --- /dev/null +++ b/dockerbuild/Dockerfile.clefos-s390x @@ -0,0 +1,20 @@ +FROM s390x/clefos:7 + +ARG go_pkg_url + +RUN yum install -y curl git wget openssh-server sudo make development-tools rpmdevtools clang gcc-c++ ruby ruby-devel && yum clean all + +RUN curl -s $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN /usr/bin/ssh-keygen -A + +RUN echo $'\n\ + export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin\n'\ + >> ~/.bash_profile + +RUN mkdir /rpmbuild && chmod 777 /rpmbuild + +CMD ["/usr/sbin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.debian-bullseye b/dockerbuild/Dockerfile.debian-bullseye new file mode 100644 index 000000000..518a08ae2 --- /dev/null +++ b/dockerbuild/Dockerfile.debian-bullseye @@ -0,0 +1,15 @@ +FROM debian:bullseye-20191224 + +ARG go_pkg_url + +RUN apt-get update && apt-get upgrade -y && apt-get -y install build-essential curl ca-certificates devscripts dh-systemd + +RUN curl -s -k $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN groupadd -g 1000 jenkins-build && useradd -u 1000 -g 1000 jenkins-build +RUN chmod 777 /home + +CMD ["/usr/bin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.debian-buster b/dockerbuild/Dockerfile.debian-buster new file mode 100644 index 000000000..6504178bc --- /dev/null +++ b/dockerbuild/Dockerfile.debian-buster @@ -0,0 +1,15 @@ +FROM debian:buster-20191224 + +ARG go_pkg_url + +RUN apt-get update && apt-get -y install build-essential curl ca-certificates devscripts dh-systemd + +RUN curl -s -k $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN groupadd -g 1000 jenkins-build && useradd -u 1000 -g 1000 jenkins-build +RUN chmod 777 /home + +CMD ["/usr/bin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.debian-jessie b/dockerbuild/Dockerfile.debian-jessie new file mode 100644 index 000000000..31b0a904b --- /dev/null +++ b/dockerbuild/Dockerfile.debian-jessie @@ -0,0 +1,15 @@ +FROM debian:jessie-20191224 + +ARG go_pkg_url + +RUN apt-get update && apt-get -y install build-essential curl ca-certificates devscripts dh-systemd + +RUN curl -s -k $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN groupadd -g 1000 jenkins-build && useradd -u 1000 -g 1000 jenkins-build +RUN chmod 777 /home + +CMD ["/usr/bin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.debian-sid b/dockerbuild/Dockerfile.debian-sid new file mode 100644 index 000000000..5fde10d5d --- /dev/null +++ b/dockerbuild/Dockerfile.debian-sid @@ -0,0 +1,15 @@ +FROM debian:sid-20191224 + +ARG go_pkg_url + +RUN apt-get update && apt-get upgrade -y && apt-get -y install build-essential curl ca-certificates devscripts dh-systemd + +RUN curl -s -k $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN groupadd -g 1000 jenkins-build && useradd -u 1000 -g 1000 jenkins-build +RUN chmod 777 /home + +CMD ["/usr/bin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.debian-stretch b/dockerbuild/Dockerfile.debian-stretch new file mode 100644 index 000000000..76342fb8d --- /dev/null +++ b/dockerbuild/Dockerfile.debian-stretch @@ -0,0 +1,15 @@ +FROM debian:stretch-20191224 + +ARG go_pkg_url + +RUN apt-get update && apt-get -y install build-essential curl ca-certificates devscripts dh-systemd + +RUN curl -s -k $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN groupadd -g 1000 jenkins-build && useradd -u 1000 -g 1000 jenkins-build +RUN chmod 777 /home + +CMD ["/usr/bin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.debian-wheezy b/dockerbuild/Dockerfile.debian-wheezy new file mode 100644 index 000000000..7322959e6 --- /dev/null +++ b/dockerbuild/Dockerfile.debian-wheezy @@ -0,0 +1,23 @@ +FROM debian:wheezy-20190228 + +ARG go_pkg_url + +RUN echo "deb http://archive.debian.org/debian/ wheezy contrib main non-free" > /etc/apt/sources.list && \ + echo "deb-src http://archive.debian.org/debian/ wheezy contrib main non-free" >> /etc/apt/sources.list && \ + apt-get update && apt-get install -y apt-utils && \ + apt-get install -y --force-yes \ + curl gcc make sudo expect gnupg fakeroot perl-base=5.14.2-21+deb7u3 perl \ + libc-bin=2.13-38+deb7u10 libc6=2.13-38+deb7u10 libc6-dev build-essential \ + cdbs devscripts equivs automake autoconf libtool libaudit-dev selinux-basics \ + libdb5.1=5.1.29-5 libdb5.1-dev libssl1.0.0=1.0.1e-2+deb7u20 procps gawk libsigsegv2 \ + curl ca-certificates devscripts + +RUN curl -s -k $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN groupadd -g 1000 jenkins-build && useradd -u 1000 -g 1000 jenkins-build +RUN chmod 777 /home + +CMD ["/usr/bin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.ubuntu-bionic b/dockerbuild/Dockerfile.ubuntu-bionic new file mode 100644 index 000000000..2bbc5b02e --- /dev/null +++ b/dockerbuild/Dockerfile.ubuntu-bionic @@ -0,0 +1,15 @@ +FROM ubuntu:bionic-20200112 + +ARG go_pkg_url + +RUN apt-get update && apt-get upgrade -y && apt-get -y install build-essential curl ca-certificates devscripts dh-systemd + +RUN curl -s -k $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN groupadd -g 1000 jenkins-build && useradd -u 1000 -g 1000 jenkins-build +RUN chmod 777 /home + +CMD ["/usr/bin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.ubuntu-eoan b/dockerbuild/Dockerfile.ubuntu-eoan new file mode 100644 index 000000000..0fd5e33ca --- /dev/null +++ b/dockerbuild/Dockerfile.ubuntu-eoan @@ -0,0 +1,15 @@ +FROM ubuntu:eoan-20200114 + +ARG go_pkg_url + +RUN apt-get update && apt-get upgrade -y && apt-get -y install build-essential curl ca-certificates devscripts dh-systemd + +RUN curl -s -k $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN groupadd -g 1000 jenkins-build && useradd -u 1000 -g 1000 jenkins-build +RUN chmod 777 /home + +CMD ["/usr/bin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.ubuntu-trusty b/dockerbuild/Dockerfile.ubuntu-trusty new file mode 100644 index 000000000..52a80c949 --- /dev/null +++ b/dockerbuild/Dockerfile.ubuntu-trusty @@ -0,0 +1,15 @@ +FROM ubuntu:trusty-20191217 + +ARG go_pkg_url + +RUN apt-get update && apt-get upgrade -y && apt-get -y install build-essential curl ca-certificates devscripts dh-systemd + +RUN curl -s -k $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN groupadd -g 1000 jenkins-build && useradd -u 1000 -g 1000 jenkins-build +RUN chmod 777 /home + +CMD ["/usr/bin/sshd", "-D"] + diff --git a/dockerbuild/Dockerfile.ubuntu-xenial b/dockerbuild/Dockerfile.ubuntu-xenial new file mode 100644 index 000000000..bda74f477 --- /dev/null +++ b/dockerbuild/Dockerfile.ubuntu-xenial @@ -0,0 +1,15 @@ +FROM ubuntu:xenial-20200114 + +ARG go_pkg_url + +RUN apt-get update && apt-get -y install build-essential curl ca-certificates devscripts dh-systemd + +RUN curl -s -k $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN groupadd -g 1000 jenkins-build && useradd -u 1000 -g 1000 jenkins-build +RUN chmod 777 /home + +CMD ["/usr/bin/sshd", "-D"] + diff --git a/dockerbuild/Makefile b/dockerbuild/Makefile new file mode 100644 index 000000000..51ef576cc --- /dev/null +++ b/dockerbuild/Makefile @@ -0,0 +1,104 @@ +.PHONY: all + +all: alpine centos debian ubuntu + +alpine: + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-arm64.tar.gz" --platform linux/arm64 -f Dockerfile.alpine . -t ztbuild/alpine-arm64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.alpine . -t ztbuild/alpine-i386 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.alpine . -t ztbuild/alpine-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v6 -f Dockerfile.alpine . -t ztbuild/alpine-armel --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v7 -f Dockerfile.alpine . -t ztbuild/alpine-armhf --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-ppc64le.tar.gz" --platform linux/ppc64le -f Dockerfile.alpine . -t ztbuild/alpine-ppc64le --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-s390x.tar.gz" --platform linux/s390x -f Dockerfile.alpine . -t ztbuild/alpine-s390x --load + +centos: + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.centos7 . -t ztbuild/centos7-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.centos7-i386 . -t ztbuild/centos7-i386 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.centos6 . -t ztbuild/centos6-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.centos6-i386 . -t ztbuild/centos6-i386 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.centos8 . -t ztbuild/centos8-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-arm64.tar.gz" --platform linux/arm64 -f Dockerfile.centos8 . -t ztbuild/centos8-arm64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-ppc64le.tar.gz" --platform linux/ppc64le -f Dockerfile.centos8 . -t ztbuild/centos8-ppc64le --load + +debian: debian-wheezy debian-jessie debian-buster debian-stretch debian-bullseye debian-sid + +debian-wheezy: + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.debian-wheezy . -t ztbuild/debian-wheezy-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v7 -f Dockerfile.debian-wheezy . -t ztbuild/debian-wheezy-armhf --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v6 -f Dockerfile.debian-wheezy . -t ztbuild/debian-wheezy-armel --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.debian-wheezy . -t ztbuild/debian-wheezy-i386 --load + +debian-jessie: + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.debian-jessie . -t ztbuild/debian-jessie-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v7 -f Dockerfile.debian-jessie . -t ztbuild/debian-jessie-armhf --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v6 -f Dockerfile.debian-jessie . -t ztbuild/debian-jessie-armel --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.debian-jessie . -t ztbuild/debian-jessie-i386 --load + +debian-buster: + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.debian-buster . -t ztbuild/debian-buster-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-arm64.tar.gz" --platform linux/arm64 -f Dockerfile.debian-buster . -t ztbuild/debian-buster-arm64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v6 -f Dockerfile.debian-buster . -t ztbuild/debian-buster-armel --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v7 -f Dockerfile.debian-buster . -t ztbuild/debian-buster-armhf --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.debian-buster . -t ztbuild/debian-buster-i386 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-ppc64le.tar.gz" --platform linux/ppc64le -f Dockerfile.debian-buster . -t ztbuild/debian-buster-ppc64le --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-s390x.tar.gz" --platform linux/s390x -f Dockerfile.debian-buster . -t ztbuild/debian-buster-s390x --load + +debian-stretch: + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.debian-stretch . -t ztbuild/debian-stretch-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-arm64.tar.gz" --platform linux/arm64 -f Dockerfile.debian-stretch . -t ztbuild/debian-stretch-arm64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v6 -f Dockerfile.debian-stretch . -t ztbuild/debian-stretch-armel --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v7 -f Dockerfile.debian-stretch . -t ztbuild/debian-stretch-armhf --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.debian-stretch . -t ztbuild/debian-stretch-i386 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-ppc64le.tar.gz" --platform linux/ppc64le -f Dockerfile.debian-stretch . -t ztbuild/debian-stretch-ppc64le --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-s390x.tar.gz" --platform linux/s390x -f Dockerfile.debian-stretch . -t ztbuild/debian-stretch-s390x --load + +debian-bullseye: + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.debian-bullseye . -t ztbuild/debian-bullseye-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-arm64.tar.gz" --platform linux/arm64 -f Dockerfile.debian-bullseye . -t ztbuild/debian-bullseye-arm64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v6 -f Dockerfile.debian-bullseye . -t ztbuild/debian-bullseye-armel --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v7 -f Dockerfile.debian-bullseye . -t ztbuild/debian-bullseye-armhf --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.debian-bullseye . -t ztbuild/debian-bullseye-i386 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-ppc64le.tar.gz" --platform linux/ppc64le -f Dockerfile.debian-bullseye . -t ztbuild/debian-bullseye-ppc64le --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-s390x.tar.gz" --platform linux/s390x -f Dockerfile.debian-bullseye . -t ztbuild/debian-bullseye-s390x --load + +debian-sid: + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.debian-sid . -t ztbuild/debian-sid-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-arm64.tar.gz" --platform linux/arm64 -f Dockerfile.debian-sid . -t ztbuild/debian-sid-arm64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v6 -f Dockerfile.debian-sid . -t ztbuild/debian-sid-armel --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v7 -f Dockerfile.debian-sid . -t ztbuild/debian-sid-armhf --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.debian-sid . -t ztbuild/debian-sid-i386 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-ppc64le.tar.gz" --platform linux/ppc64le -f Dockerfile.debian-sid . -t ztbuild/debian-sid-ppc64le --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-s390x.tar.gz" --platform linux/s390x -f Dockerfile.debian-sid . -t ztbuild/debian-sid-s390x --load + +ubuntu: ubuntu-trusty ubuntu-xenial ubuntu-bionic ubuntu-eoan + +ubuntu-trusty: + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.ubuntu-trusty . -t ztbuild/ubuntu-trusty-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-arm64.tar.gz" --platform linux/arm64 -f Dockerfile.ubuntu-trusty . -t ztbuild/ubuntu-trusty-arm64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v7 -f Dockerfile.ubuntu-trusty . -t ztbuild/ubuntu-trusty-armhf --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.ubuntu-trusty . -t ztbuild/ubuntu-trusty-i386 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-ppc64le.tar.gz" --platform linux/ppc64le -f Dockerfile.ubuntu-trusty . -t ztbuild/ubuntu-trusty-ppc64le --load + +ubuntu-xenial: + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.ubuntu-xenial . -t ztbuild/ubuntu-xenial-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-arm64.tar.gz" --platform linux/arm64 -f Dockerfile.ubuntu-xenial . -t ztbuild/ubuntu-xenial-arm64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v7 -f Dockerfile.ubuntu-xenial . -t ztbuild/ubuntu-xenial-armhf --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.ubuntu-xenial . -t ztbuild/ubuntu-xenial-i386 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-ppc64le.tar.gz" --platform linux/ppc64le -f Dockerfile.ubuntu-xenial . -t ztbuild/ubuntu-xenial-ppc64le --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-s390x.tar.gz" --platform linux/s390x -f Dockerfile.ubuntu-xenial . -t ztbuild/ubuntu-xenial-s390x --load + +ubuntu-bionic: + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.ubuntu-bionic . -t ztbuild/ubuntu-bionic-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-arm64.tar.gz" --platform linux/arm64 -f Dockerfile.ubuntu-bionic . -t ztbuild/ubuntu-bionic-arm64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v7 -f Dockerfile.ubuntu-bionic . -t ztbuild/ubuntu-bionic-armhf --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.ubuntu-bionic . -t ztbuild/ubuntu-bionic-i386 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-ppc64le.tar.gz" --platform linux/ppc64le -f Dockerfile.ubuntu-bionic . -t ztbuild/ubuntu-bionic-ppc64le --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-s390x.tar.gz" --platform linux/s390x -f Dockerfile.ubuntu-bionic . -t ztbuild/ubuntu-bionic-s390x --load + +ubuntu-eoan: + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.ubuntu-eoan . -t ztbuild/ubuntu-eoan-amd64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-arm64.tar.gz" --platform linux/arm64 -f Dockerfile.ubuntu-eoan . -t ztbuild/ubuntu-eoan-arm64 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz" --platform linux/arm/v7 -f Dockerfile.ubuntu-eoan . -t ztbuild/ubuntu-eoan-armhf --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.ubuntu-eoan . -t ztbuild/ubuntu-eoan-i386 --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-ppc64le.tar.gz" --platform linux/ppc64le -f Dockerfile.ubuntu-eoan . -t ztbuild/ubuntu-eoan-ppc64le --load + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-s390x.tar.gz" --platform linux/s390x -f Dockerfile.ubuntu-eoan . -t ztbuild/ubuntu-eoan-s390x --load diff --git a/dockerbuild/authorized_keys b/dockerbuild/authorized_keys new file mode 100644 index 000000000..0dd35c7b7 --- /dev/null +++ b/dockerbuild/authorized_keys @@ -0,0 +1,2 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8hgysbj2Luu3aN/Ya2wr4Y9LpUGqWWfn3k+UhIwOIE/Kd7/YpLjxHpseUA1hLnj9kHFShH8eiqoY0S6EDIYrTUwbXMMu8454lX/LcJOCJ9RlSeMMf7vpkxcI7cVRgOA430a3FR7M0Q8vKlyJzxxAEjMIxMyuVyinknfanNt+sQFiDUvOXoacqgZAHBWMlO7wOPyHWHNOzy7g8N0dHiJveKZqX/UUwuqJuS6UBq7MBMSU6TcMvJwHr+AbNvfyIUWCqlTByqFL9cmviRbIvQanxoRxi/5fVUGhtVBXUYvbCdFxDw5W2Svo9fDMm4Z5xWAD7rY1J3AM15RVyRTTtYvgD + diff --git a/dockerbuild/pipelint.sh b/dockerbuild/pipelint.sh new file mode 100644 index 000000000..7fbd0de82 --- /dev/null +++ b/dockerbuild/pipelint.sh @@ -0,0 +1,13 @@ +# curl (REST API) +# User +JENKINS_USER=grant + +# Api key from "/me/configure" on my Jenkins instance +JENKINS_USER_KEY=11edf2d49321321119712c46c6349eaad7 + +# Url for my local Jenkins instance. +JENKINS_URL=http://$JENKINS_USER:$JENKINS_USER_KEY@jenkins.int.zerotier.com + +# JENKINS_CRUMB is needed if your Jenkins master has CRSF protection enabled (which it should) +JENKINS_CRUMB=`curl "$JENKINS_URL/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,\":\",//crumb)"` +curl -X POST -H $JENKINS_CRUMB -F "jenkinsfile= Date: Mon, 3 Feb 2020 10:46:39 -0800 Subject: [PATCH 034/362] add kali rolling --- dockerbuild/Dockerfile.kali-rolling | 15 +++++++++++++++ dockerbuild/Makefile | 6 +++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 dockerbuild/Dockerfile.kali-rolling diff --git a/dockerbuild/Dockerfile.kali-rolling b/dockerbuild/Dockerfile.kali-rolling new file mode 100644 index 000000000..2825dffe4 --- /dev/null +++ b/dockerbuild/Dockerfile.kali-rolling @@ -0,0 +1,15 @@ +FROM kalilinux/kali-rolling:latest + +ARG go_pkg_url + +RUN apt-get update && apt-get upgrade -y && apt-get -y install build-essential curl ca-certificates devscripts dh-systemd cmake + +RUN curl -s -k $go_pkg_url -o go.tar.gz && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz + +RUN groupadd -g 1000 jenkins-build && useradd -u 1000 -g 1000 jenkins-build +RUN chmod 777 /home + +CMD ["/usr/bin/sshd", "-D"] + diff --git a/dockerbuild/Makefile b/dockerbuild/Makefile index 51ef576cc..543d45d5a 100644 --- a/dockerbuild/Makefile +++ b/dockerbuild/Makefile @@ -1,6 +1,6 @@ .PHONY: all -all: alpine centos debian ubuntu +all: alpine centos debian ubuntu kali-rolling alpine: @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-arm64.tar.gz" --platform linux/arm64 -f Dockerfile.alpine . -t ztbuild/alpine-arm64 --load @@ -102,3 +102,7 @@ ubuntu-eoan: @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-386.tar.gz" --platform linux/386 -f Dockerfile.ubuntu-eoan . -t ztbuild/ubuntu-eoan-i386 --load @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-ppc64le.tar.gz" --platform linux/ppc64le -f Dockerfile.ubuntu-eoan . -t ztbuild/ubuntu-eoan-ppc64le --load @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-s390x.tar.gz" --platform linux/s390x -f Dockerfile.ubuntu-eoan . -t ztbuild/ubuntu-eoan-s390x --load + +kali-rolling: + @docker buildx build --build-arg go_pkg_url="https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz" --platform linux/amd64 -f Dockerfile.kali-rolling . -t ztbuild/kali-rolling-amd64 --load + From ba4324f9923774d96351c07a72c9bb43fb00d11b Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 3 Feb 2020 12:51:46 -0800 Subject: [PATCH 035/362] Update jenkinsfile for new build process --- Jenkinsfile | 366 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 340 insertions(+), 26 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 81f7c8156..757729e33 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,18 +1,71 @@ -#!/usr/bin/env groovy - -def alpineStaticTask(distro, platform) { - def myNode = { - node ('linux-build') { - checkout scm - def runtime = docker.image("ztbuild/${distro}-${platform}:latest") - runtime.inside { - sh 'make -j8 ZT_STATIC=1 all' - sh "mv zerotier-one zerotier-one-static-${platform}" - archiveArtifacts artifacts: 'zerotier-one-*', fingerprint: true, onlyIfSuccessful: true +pipeline { + options { + disableConcurrentBuilds() + preserveStashes(buildCount: 10) + timestamps() + } + parameters { + booleanParam(name: "BUILD_ALL", defaultValue: false, description: "Build all supported platform/architecture combos. Defaults to x86/x64 only") + } + + agent none + + stages { + stage ("Build") { + steps { + script { + def tasks = [:] + tasks << buildStaticBinaries() + tasks << buildDebianNative() + tasks << buildCentosNative() + + parallel tasks + } + } + } + stage ("Package Static") { + steps { + script { + parallel packageStatic() + } } } } - return myNode +} + +def buildStaticBinaries() { + def tasks = [:] + def dist = ["alpine"] + def archs = [] + if (params.BUILD_ALL == true) { + archs = ["arm64", "amd64", "i386", "armhf", "armel", "ppc64le", "s390x"] + } else { + archs = ["amd64", "i386"] + } + + tasks << getTasks(dist, archs, { distro, platform -> + def myNode = { + node ('linux-build') { + dir ("build") { + checkout scm + } + sh "echo ${distro}-${platform}" + def runtime = docker.image("ztbuild/${distro}-${platform}:latest") + runtime.inside { + dir("build") { + sh 'make -j8 ZT_STATIC=1 all' + sh "file ./zerotier-one" + sh "mv zerotier-one zerotier-one-static-${platform}" + stash includes: 'zerotier-one-static-*', name: "static-${platform}" + } + cleanWs deleteDirs: true, disableDeferredWipeout: true, notFailBuild: true + } + } + } + return myNode + }) + + return tasks } def getTasks(axisDistro, axisPlatform, task) { @@ -27,25 +80,286 @@ def getTasks(axisDistro, axisPlatform, task) { return tasks } -pipeline { - options { - disableConcurrentBuilds() +def packageStatic() { + def tasks = [:] + + def centos6 = ["centos6"] + def centos6Arch = ["i386", "amd64"] + tasks << getTasks(centos6, centos6Arch, { distro, arch -> + def myNode = { + node ('linux-build') { + dir ("build") { + checkout scm + } + def runtime = docker.image("ztbuild/${distro}-${arch}:latest") + runtime.inside { + dir("build") { + unstash "static-${arch}" + sh "mv zerotier-one-static-${arch} zerotier-one && chmod +x zerotier-one" + sh "make redhat" + sh "mkdir -p ${distro}" + sh "cp -av `find ~/rpmbuild/ -type f -name \"*.rpm\"` ${distro}/" + archiveArtifacts artifacts: "${distro}/*.rpm", onlyIfSuccessful: true + } + } + cleanWs deleteDirs: true, disableDeferredWipeout: true, notFailBuild: true + } + } + return myNode + }) + + def centos7 = ["centos7"] + def centos7Arch = ["i386"] + tasks << getTasks(centos7, centos7Arch, { distro, arch -> + def myNode = { + node ('linux-build') { + dir ("build") { + checkout scm + } + def runtime = docker.image("ztbuild/${distro}-${arch}:latest") + runtime.inside { + dir("build") { + unstash "static-${arch}" + sh "mv zerotier-one-static-${arch} zerotier-one && chmod +x zerotier-one" + sh "make redhat" + sh "mkdir -p ${distro}" + sh "cp -av `find ~/rpmbuild/ -type f -name \"*.rpm\"` ${distro}/" + archiveArtifacts artifacts: "${distro}/*.rpm", onlyIfSuccessful: true + } + } + cleanWs deleteDirs: true, disableDeferredWipeout: true, notFailBuild: true + } + } + return myNode + }) + + if (params.BUILD_ALL == true) { + def clefos7 = ["clefos"] + def clefos7Arch = ["s390x"] + tasks << getTasks(clefos7, clefos7Arch, { distro, arch -> + def myNode = { + node ('linux-build') { + dir ("build") { + checkout scm + } + def runtime = docker.image("ztbuild/${distro}-${arch}:latest") + runtime.inside { + dir("build/") { + unstash "static-${arch}" + sh "mv zerotier-one-static-${arch} zerotier-one && chmod +x zerotier-one" + sh "make redhat" + sh "mkdir -p ${distro}" + sh "cp -av `find ~/rpmbuild/ -type f -name \"*.rpm\"` ${distro}/" + archiveArtifacts artifacts: "${distro}/*.rpm", onlyIfSuccessful: true + } + } + cleanWs deleteDirs: true, disableDeferredWipeout: true, notFailBuild: true + } + } + return myNode + }) + } + + def debianJessie = ["debian-jessie"] + def debianJessieArchs = [] + if (params.BUILD_ALL == true) { + debianJessieArch = ["armhf", "armel", "amd64", "i386"] + } else { + debianJessieArch = ["amd64", "i386"] + } + tasks << getTasks(debianJessie, debianJessieArch, { distro, arch -> + def myNode = { + node ('linux-build') { + dir ("build") { + checkout scm + } + def runtime = docker.image("ztbuild/${distro}-${arch}:latest") + runtime.inside { + sh "ls -la ." + dir('build/') { + sh "ls -la ." + unstash "static-${arch}" + sh "pwd" + sh "mv zerotier-one-static-${arch} zerotier-one && chmod +x zerotier-one && file ./zerotier-one" + sh "mv -f debian/rules.static debian/rules" + sh "make debian" + } + sh "mkdir -p ${distro}" + sh "mv *.deb ${distro}" + archiveArtifacts artifacts: "${distro}/*.deb", onlyIfSuccessful: true + } + cleanWs deleteDirs: true, disableDeferredWipeout: true, notFailBuild: true + } + } + return myNode + }) + + def ubuntuTrusty = ["ubuntu-trusty"] + def ubuntuTrustyArch = [] + if (params.BUILD_ALL == true) { + ubuntuTrustyArch = ["i386", "amd64", "armhf", "arm64", "ppc64le"] + } else { + ubuntuTrustyArch = ["i386", "amd64"] + } + tasks << getTasks(ubuntuTrusty, ubuntuTrustyArch, { distro, arch -> + def myNode = { + node ('linux-build') { + dir ("build") { + checkout scm + } + def runtime = docker.image("ztbuild/${distro}-${arch}:latest") + runtime.inside { + sh "ls -la ." + dir('build/') { + sh "ls -la ." + unstash "static-${arch}" + sh "pwd" + sh "mv zerotier-one-static-${arch} zerotier-one && chmod +x zerotier-one && file ./zerotier-one" + sh "mv -f debian/rules.static debian/rules" + sh "make debian" + } + sh "mkdir -p ${distro}" + sh "mv *.deb ${distro}" + archiveArtifacts artifacts: "${distro}/*.deb", onlyIfSuccessful: true + } + cleanWs deleteDirs: true, disableDeferredWipeout: true, notFailBuild: true + } + } + return myNode + }) + + def debianWheezy = ["debian-wheezy"] + def debianWheezyArchs = [] + if (params.BUILD_ALL == true) { + debianWheezyArchs = ["armhf", "armel", "amd64", "i386"] + } else { + debianWheezyArchs = ["amd64", "i386"] + } + tasks << getTasks(debianJessie, debianJessieArch, { distro, arch -> + def myNode = { + node ('linux-build') { + dir ("build") { + checkout scm + } + def runtime = docker.image("ztbuild/${distro}-${arch}:latest") + runtime.inside { + dir('build/') { + unstash "static-${arch}" + sh "mv zerotier-one-static-${arch} zerotier-one && chmod +x zerotier-one && file ./zerotier-one" + sh "mv -f debian/rules.wheezy.static debian/rules" + sh "mv -f debian/control.wheezy debian/control" + sh "make debian" + } + sh "mkdir -p ${distro}" + sh "mv *.deb ${distro}" + archiveArtifacts artifacts: "${distro}/*.deb", onlyIfSuccessful: true + } + cleanWs deleteDirs: true, disableDeferredWipeout: true, notFailBuild: true + } + } + return myNode + }) + + return tasks +} + +def buildDebianNative() { + def tasks = [:] + def buster = ["debian-buster", "debian-stretch", "debian-bullseye", "debian-sid"] + def busterArchs = [] + if (params.BUILD_ALL) { + busterArchs = ["s390x", "ppc64le", "i386", "armhf", "armel", "arm64", "amd64"] + } else { + busterArchs = ["amd64", "i386"] } - agent none - - stages { - stage ("Static Build") { - steps { - script { - def dist = ["alpine"] - def archs = ["aarch64", "amd64", "i386", "armhf", "armel", "ppc64le", "s390x"] - parallel getTasks(dist, archs, this.&alpineStaticTask) + def build = { distro, arch -> + def myNode = { + node ('linux-build') { + dir ("build") { + checkout scm + } + def runtime = docker.image("ztbuild/${distro}-${arch}:latest") + runtime.inside { + dir("build") { + sh 'make debian' + } + sh "mkdir -p ${distro}" + sh "mv *.deb ${distro}" + archiveArtifacts artifacts: "${distro}/*.deb", onlyIfSuccessful: true + cleanWs deleteDirs: true, disableDeferredWipeout: true, notFailBuild: true } } } + return myNode } + + tasks << getTasks(buster, busterArchs, build) + + // bash is broken when running under QEMU-s390x on Xenial + def xenial = ["ubuntu-xenial"] + def xenialArchs = [] + if (params.BUILD_ALL == true) { + xenialArchs = ["i386", "amd64", "armhf", "arm64", "ppc64le"] + } else { + xenialArchs = ["i386", "amd64"] + } + tasks << getTasks(xenial, xenialArchs, build) + + def ubuntu = ["ubuntu-bionic", "ubuntu-eoan"] + def ubuntuArchs = [] + if (params.BUILD_ALL == true) { + ubuntuArchs = ["i386", "amd64", "armhf", "arm64", "ppc64le", "s390x"] + } else { + ubuntuArchs = ["i386", "amd64"] + } + tasks << getTasks(ubuntu, ubuntuArchs, build) + + def kali = ["kali-rolling"] + def kaliArchs = ["amd64"] + tasks << getTasks(kali, kaliArchs, build) + + return tasks } - -mattermostSend color: "#00ff00", message: "${env.JOB_NAME} #${env.BUILD_NUMBER} Complete (<${env.BUILD_URL}|Show More...>)" +def buildCentosNative() { + def tasks = [:] + + def build = { distro, arch -> + def myNode = { + node ('linux-build') { + dir ("build") { + checkout scm + } + def runtime = docker.image("ztbuild/${distro}-${arch}:latest") + runtime.inside { + dir("build") { + sh 'make -j4' + sh 'make redhat' + sh "mkdir -p ${distro}" + sh "cp -av `find ~/rpmbuild/ -type f -name \"*.rpm\"` ${distro}/" + archiveArtifacts artifacts: "${distro}/*.rpm", onlyIfSuccessful: true + } + + cleanWs deleteDirs: true, disableDeferredWipeout: true, notFailBuild: true + } + } + } + return myNode + } + + def centos8 = ["centos8"] + def centos8Archs = [] + if (params.BUILD_ALL == true) { + centos8Archs = ["amd64", "arm64", "ppc64le"] + } else { + centos8Archs = ["amd64"] + } + tasks << getTasks(centos8, centos8Archs, build) + + def centos7 = ["centos7"] + def centos7Archs = ["amd64"] + tasks << getTasks(centos7, centos7Archs, build) + + return tasks +} From baa14a63c9689757cd69a2c7322db4f871614be9 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 24 Feb 2020 10:20:47 -0800 Subject: [PATCH 036/362] It's LOG! It's LOG! It's big, it's heavy, it's wood --- controller/PostgreSQL.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 8f8c2785a..d1f8ef218 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1017,6 +1017,10 @@ void PostgreSQL::commitThread() get(nwidInt, nwOrig, memberidInt, memOrig); + if(memOrig["authorized"] && ! (*config)["authorized"]) { + fprintf(stderr, "Member %s deauthed\n", (*config)["id"].get().c_str()); + } + _memberChanged(memOrig, memNew, qitem.second); } else { fprintf(stderr, "Can't notify of change. Error parsing nwid or memberid: %llu-%llu\n", (unsigned long long)nwidInt, (unsigned long long)memberidInt); From 12a92fc4a11d21506659e4a549ed65b0a22f6ee7 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 24 Feb 2020 11:02:05 -0800 Subject: [PATCH 037/362] Un-log & some production build image fixes --- controller/PostgreSQL.cpp | 4 ---- ext/central-controller-docker/Dockerfile | 2 ++ make-linux.mk | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index d1f8ef218..8f8c2785a 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1017,10 +1017,6 @@ void PostgreSQL::commitThread() get(nwidInt, nwOrig, memberidInt, memOrig); - if(memOrig["authorized"] && ! (*config)["authorized"]) { - fprintf(stderr, "Member %s deauthed\n", (*config)["id"].get().c_str()); - } - _memberChanged(memOrig, memNew, qitem.second); } else { fprintf(stderr, "Can't notify of change. Error parsing nwid or memberid: %llu-%llu\n", (unsigned long long)nwidInt, (unsigned long long)memberidInt); diff --git a/ext/central-controller-docker/Dockerfile b/ext/central-controller-docker/Dockerfile index 716390f82..8ded3e64a 100644 --- a/ext/central-controller-docker/Dockerfile +++ b/ext/central-controller-docker/Dockerfile @@ -16,6 +16,8 @@ RUN ldconfig RUN cd ZeroTierOne && make central-controller FROM centos:7 +RUN yum install -y https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-centos10-10-2.noarch.rpm && yum -y install epel-release && yum -y update && yum clean all +RUN yum install -y jemalloc jemalloc-devel postgresql10 COPY --from=builder /ZeroTierOne/zerotier-one /usr/local/bin/zerotier-one RUN chmod a+x /usr/local/bin/zerotier-one diff --git a/make-linux.mk b/make-linux.mk index 8b63d7998..de96f2290 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -329,7 +329,7 @@ official: FORCE make -j4 ZT_OFFICIAL=1 all docker: FORCE - docker build -f ext/installfiles/linux/zerotier-containerized/Dockerfile -t zerotier-containerized . + docker build --no-cache -f ext/installfiles/linux/zerotier-containerized/Dockerfile -t zerotier-containerized . central-controller: FORCE make -j4 LDLIBS="-L/usr/pgsql-10/lib/ -lpq -Lext/librabbitmq/centos_x64/lib/ -lrabbitmq" CXXFLAGS="-I/usr/pgsql-10/include -I./ext/librabbitmq/centos_x64/include -fPIC" DEFS="-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER" ZT_OFFICIAL=1 ZT_USE_X64_ASM_ED25519=1 one From 00ac35ad6262ac11e548498e9d3c0cb3cd4262ca Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 24 Feb 2020 11:03:06 -0800 Subject: [PATCH 038/362] add --no-cache for docker central controller build --- make-linux.mk | 2 +- make-mac.mk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/make-linux.mk b/make-linux.mk index de96f2290..25cf5249a 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -335,7 +335,7 @@ central-controller: FORCE make -j4 LDLIBS="-L/usr/pgsql-10/lib/ -lpq -Lext/librabbitmq/centos_x64/lib/ -lrabbitmq" CXXFLAGS="-I/usr/pgsql-10/include -I./ext/librabbitmq/centos_x64/include -fPIC" DEFS="-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER" ZT_OFFICIAL=1 ZT_USE_X64_ASM_ED25519=1 one central-controller-docker: FORCE - docker build -t docker.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=`git name-rev --name-only HEAD` . + docker build --no-cache -t docker.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=`git name-rev --name-only HEAD` . debug: FORCE make ZT_DEBUG=1 one diff --git a/make-mac.mk b/make-mac.mk index 1f101ff2a..35ca4a486 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -150,7 +150,7 @@ official: FORCE make ZT_OFFICIAL_RELEASE=1 mac-dist-pkg central-controller-docker: FORCE - docker build -t docker.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . + docker build --no-cache -t docker.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . clean: rm -rf MacEthernetTapAgent *.dSYM build-* *.a *.pkg *.dmg *.o node/*.o controller/*.o service/*.o osdep/*.o ext/http-parser/*.o $(CORE_OBJS) $(ONE_OBJS) zerotier-one zerotier-idtool zerotier-selftest zerotier-cli zerotier doc/node_modules macui/build zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_* From e70294204127a670af1f7ce8bf08f0470c9063bb Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 3 Mar 2020 13:34:24 -0800 Subject: [PATCH 039/362] Fix duplicate assigned IP check when adding assigned IPs to database --- controller/PostgreSQL.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 8f8c2785a..be9083303 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -998,6 +998,7 @@ void PostgreSQL::commitThread() PQclear(PQexec(conn, "ROLLBACK")); break;; } + assignments.push_back(addr); } res = PQexec(conn, "COMMIT"); From f6026f94a5d546dd454b1c69481772d55c6bc13b Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 3 Mar 2020 23:52:53 -0800 Subject: [PATCH 040/362] More fun with dupes --- controller/PostgreSQL.cpp | 7 ++++++- ext/central-controller-docker/Dockerfile | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index be9083303..2be90f448 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -530,7 +530,12 @@ void PostgreSQL::initializeMembers(PGconn *conn) int n = PQntuples(r2); for (int j = 0; j < n; ++j) { - config["ipAssignments"].push_back(PQgetvalue(r2, j, 0)); + std::string ipaddr = PQgetvalue(r2, j, 0); + std::size_t pos = ipaddr.find('/'); + if (pos != std::string::npos) { + ipaddr = ipaddr.substr(0, pos); + } + config["ipAssignments"].push_back(ipaddr); } _memberChanged(empty, config, false); diff --git a/ext/central-controller-docker/Dockerfile b/ext/central-controller-docker/Dockerfile index 8ded3e64a..12984ebda 100644 --- a/ext/central-controller-docker/Dockerfile +++ b/ext/central-controller-docker/Dockerfile @@ -10,10 +10,10 @@ RUN yum -y install epel-release && yum -y update && yum clean all RUN yum groupinstall -y "Development Tools" RUN yum install -y bash postgresql10 postgresql10-devel libpqxx-devel glibc-static libstdc++-static clang jemalloc jemalloc-devel -RUN git clone http://git.int.zerotier.com/zerotier/ZeroTierOne.git -RUN if [ "$git_branch" != "master" ]; then cd ZeroTierOne && git checkout -b $git_branch origin/$git_branch; fi -RUN ldconfig -RUN cd ZeroTierOne && make central-controller +# RUN git clone http://git.int.zerotier.com/zerotier/ZeroTierOne.git +# RUN if [ "$git_branch" != "master" ]; then cd ZeroTierOne && git checkout -b $git_branch origin/$git_branch; fi +ADD . /ZeroTierOne +RUN cd ZeroTierOne && make clean && make central-controller FROM centos:7 RUN yum install -y https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-centos10-10-2.noarch.rpm && yum -y install epel-release && yum -y update && yum clean all From 0d4210825497aa6293030846513312ac8b6c297b Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 4 Mar 2020 09:45:45 -0800 Subject: [PATCH 041/362] member ip inserts DO NOTHING on conflict --- controller/PostgreSQL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 2be90f448..6978bef35 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -989,7 +989,7 @@ void PostgreSQL::commitThread() }; res = PQexecParams(conn, - "INSERT INTO ztc_member_ip_assignment (member_id, network_id, address) VALUES ($1, $2, $3)", + "INSERT INTO ztc_member_ip_assignment (member_id, network_id, address) VALUES ($1, $2, $3) ON CONFLICT (network_id, member_id, address) DO NOTHING", 3, NULL, v3, From 54f9f2ad532d859deb5bf257a8e4bad4343a64b6 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 31 Mar 2020 20:27:41 -0700 Subject: [PATCH 042/362] Add special case for Ubiquiti routers in platformDefaultHomePath() --- make-linux.mk | 3 +++ osdep/OSUtils.cpp | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/make-linux.mk b/make-linux.mk index 25cf5249a..e74f19303 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -85,6 +85,9 @@ endif ifeq ($(ZT_QNAP), 1) override DEFS+=-D__QNAP__ endif +ifeq ($(ZT_UBIQUITI), 1) + override DEFS+=-D__UBIQUITI__ +endif ifeq ($(ZT_SYNOLOGY), 1) override CFLAGS+=-fPIC diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp index 0299b12bc..e3fa63730 100644 --- a/osdep/OSUtils.cpp +++ b/osdep/OSUtils.cpp @@ -385,6 +385,10 @@ std::string OSUtils::platformDefaultHomePath() homeDir.erase(std::remove(homeDir.begin(), homeDir.end(), '\n'), homeDir.end()); return homeDir; #endif +#ifdef __UBIQUITI__ + // Only persistent location after firmware upgrades + return std::string("/config/zerotier-one"); +#else // Check for user-defined environment variable before using defaults #ifdef __WINDOWS__ From 32a78b5f610e87c2517fda1a4b0040966ef2c839 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 31 Mar 2020 20:34:48 -0700 Subject: [PATCH 043/362] Fix preprocessor typo in previous commit --- osdep/OSUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp index e3fa63730..3770f0217 100644 --- a/osdep/OSUtils.cpp +++ b/osdep/OSUtils.cpp @@ -388,7 +388,7 @@ std::string OSUtils::platformDefaultHomePath() #ifdef __UBIQUITI__ // Only persistent location after firmware upgrades return std::string("/config/zerotier-one"); -#else +#endif // Check for user-defined environment variable before using defaults #ifdef __WINDOWS__ From 182f16d2a4f59d6a8420803fc202b6a75285465e Mon Sep 17 00:00:00 2001 From: Athanasios Oikonomou Date: Sun, 26 Apr 2020 16:53:49 +0200 Subject: [PATCH 044/362] Add support for armv7ve This commit adds support for ARM armv7ve arch. The extended version of the ARMv7-A architecture with support for virtualization. --- make-linux.mk | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/make-linux.mk b/make-linux.mk index 9fd4f7b95..eff3a50ed 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -205,6 +205,11 @@ ifeq ($(CC_MACH),armv7hl) override DEFS+=-DZT_NO_TYPE_PUNNING ZT_USE_ARM32_NEON_ASM_CRYPTO=1 endif +ifeq ($(CC_MACH),armv7ve) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_CRYPTO=1 +endif ifeq ($(CC_MACH),arm64) ZT_ARCHITECTURE=4 override DEFS+=-DZT_NO_TYPE_PUNNING From d2db307bee4891a4b1744ae205c794fd0ce56f98 Mon Sep 17 00:00:00 2001 From: Gleb Panov Date: Wed, 29 Apr 2020 13:51:28 +0300 Subject: [PATCH 045/362] Add lines to enable Elbrus (https://en.wikipedia.org/wiki/Elbrus-8S) microprocessor architecture. No changes in code, only in build procedure (recognise relevant ARCH output. --- make-linux.mk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/make-linux.mk b/make-linux.mk index d48697067..39ffbe73e 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -146,6 +146,9 @@ endif ifeq ($(CC_MACH),ppc64el) ZT_ARCHITECTURE=8 endif +ifeq ($(CC_MACH),e2k) + ZT_ARCHITECTURE=2 +endif ifeq ($(CC_MACH),i386) ZT_ARCHITECTURE=1 endif From acb4ef0f1265534c2fe4672ec58c6fffffa55dad Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 11 May 2020 11:48:05 -0700 Subject: [PATCH 046/362] add hiredis-vip to controller build --- controller/PostgreSQL.cpp | 9 + controller/PostgreSQL.hpp | 3 + ext/central-controller-docker/Dockerfile | 4 +- ext/hiredis-vip-0.3.0/.gitignore | 7 + ext/hiredis-vip-0.3.0/.travis.yml | 16 + ext/hiredis-vip-0.3.0/CHANGELOG.md | 16 + ext/hiredis-vip-0.3.0/COPYING | 29 + ext/hiredis-vip-0.3.0/Makefile | 205 + ext/hiredis-vip-0.3.0/README.md | 255 + ext/hiredis-vip-0.3.0/adapters/ae.h | 154 + ext/hiredis-vip-0.3.0/adapters/glib.h | 153 + ext/hiredis-vip-0.3.0/adapters/libev.h | 147 + ext/hiredis-vip-0.3.0/adapters/libevent.h | 135 + ext/hiredis-vip-0.3.0/adapters/libuv.h | 122 + ext/hiredis-vip-0.3.0/adlist.c | 341 ++ ext/hiredis-vip-0.3.0/adlist.h | 93 + ext/hiredis-vip-0.3.0/async.c | 691 +++ ext/hiredis-vip-0.3.0/async.h | 130 + ext/hiredis-vip-0.3.0/command.c | 1700 ++++++ ext/hiredis-vip-0.3.0/command.h | 179 + ext/hiredis-vip-0.3.0/crc16.c | 87 + ext/hiredis-vip-0.3.0/dict.c | 338 ++ ext/hiredis-vip-0.3.0/dict.h | 126 + ext/hiredis-vip-0.3.0/examples/example-ae.c | 62 + ext/hiredis-vip-0.3.0/examples/example-glib.c | 73 + .../examples/example-libev.c | 52 + .../examples/example-libevent.c | 53 + .../examples/example-libuv.c | 53 + ext/hiredis-vip-0.3.0/examples/example.c | 78 + ext/hiredis-vip-0.3.0/fmacros.h | 23 + ext/hiredis-vip-0.3.0/hiarray.c | 188 + ext/hiredis-vip-0.3.0/hiarray.h | 56 + ext/hiredis-vip-0.3.0/hircluster.c | 4747 +++++++++++++++++ ext/hiredis-vip-0.3.0/hircluster.h | 178 + ext/hiredis-vip-0.3.0/hiredis.c | 1021 ++++ ext/hiredis-vip-0.3.0/hiredis.h | 221 + ext/hiredis-vip-0.3.0/hiutil.c | 554 ++ ext/hiredis-vip-0.3.0/hiutil.h | 265 + ext/hiredis-vip-0.3.0/net.c | 458 ++ ext/hiredis-vip-0.3.0/net.h | 53 + ext/hiredis-vip-0.3.0/read.c | 525 ++ ext/hiredis-vip-0.3.0/read.h | 129 + ext/hiredis-vip-0.3.0/sds.c | 1095 ++++ ext/hiredis-vip-0.3.0/sds.h | 105 + ext/hiredis-vip-0.3.0/test.c | 806 +++ ext/hiredis-vip-0.3.0/win32.h | 42 + make-linux.mk | 9 +- make-mac.mk | 7 +- 48 files changed, 15788 insertions(+), 5 deletions(-) create mode 100644 ext/hiredis-vip-0.3.0/.gitignore create mode 100644 ext/hiredis-vip-0.3.0/.travis.yml create mode 100644 ext/hiredis-vip-0.3.0/CHANGELOG.md create mode 100644 ext/hiredis-vip-0.3.0/COPYING create mode 100644 ext/hiredis-vip-0.3.0/Makefile create mode 100644 ext/hiredis-vip-0.3.0/README.md create mode 100644 ext/hiredis-vip-0.3.0/adapters/ae.h create mode 100644 ext/hiredis-vip-0.3.0/adapters/glib.h create mode 100644 ext/hiredis-vip-0.3.0/adapters/libev.h create mode 100644 ext/hiredis-vip-0.3.0/adapters/libevent.h create mode 100644 ext/hiredis-vip-0.3.0/adapters/libuv.h create mode 100644 ext/hiredis-vip-0.3.0/adlist.c create mode 100644 ext/hiredis-vip-0.3.0/adlist.h create mode 100644 ext/hiredis-vip-0.3.0/async.c create mode 100644 ext/hiredis-vip-0.3.0/async.h create mode 100644 ext/hiredis-vip-0.3.0/command.c create mode 100644 ext/hiredis-vip-0.3.0/command.h create mode 100644 ext/hiredis-vip-0.3.0/crc16.c create mode 100644 ext/hiredis-vip-0.3.0/dict.c create mode 100644 ext/hiredis-vip-0.3.0/dict.h create mode 100644 ext/hiredis-vip-0.3.0/examples/example-ae.c create mode 100644 ext/hiredis-vip-0.3.0/examples/example-glib.c create mode 100644 ext/hiredis-vip-0.3.0/examples/example-libev.c create mode 100644 ext/hiredis-vip-0.3.0/examples/example-libevent.c create mode 100644 ext/hiredis-vip-0.3.0/examples/example-libuv.c create mode 100644 ext/hiredis-vip-0.3.0/examples/example.c create mode 100644 ext/hiredis-vip-0.3.0/fmacros.h create mode 100644 ext/hiredis-vip-0.3.0/hiarray.c create mode 100644 ext/hiredis-vip-0.3.0/hiarray.h create mode 100644 ext/hiredis-vip-0.3.0/hircluster.c create mode 100644 ext/hiredis-vip-0.3.0/hircluster.h create mode 100644 ext/hiredis-vip-0.3.0/hiredis.c create mode 100644 ext/hiredis-vip-0.3.0/hiredis.h create mode 100644 ext/hiredis-vip-0.3.0/hiutil.c create mode 100644 ext/hiredis-vip-0.3.0/hiutil.h create mode 100644 ext/hiredis-vip-0.3.0/net.c create mode 100644 ext/hiredis-vip-0.3.0/net.h create mode 100644 ext/hiredis-vip-0.3.0/read.c create mode 100644 ext/hiredis-vip-0.3.0/read.h create mode 100644 ext/hiredis-vip-0.3.0/sds.c create mode 100644 ext/hiredis-vip-0.3.0/sds.h create mode 100644 ext/hiredis-vip-0.3.0/test.c create mode 100644 ext/hiredis-vip-0.3.0/win32.h diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 6978bef35..e81ac0a69 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -740,6 +740,11 @@ void PostgreSQL::_membersWatcher_RabbitMQ() { } } +void PostgreSQL::_membersWatcher_Reids() { + char buff[11] = {0}; + +} + void PostgreSQL::networksDbWatcher() { PGconn *conn = getPgConn(NO_OVERRIDE); @@ -844,6 +849,10 @@ void PostgreSQL::_networksWatcher_RabbitMQ() { } } +void PostgreSQL::_networksWatcher_Redis() { + +} + void PostgreSQL::commitThread() { PGconn *conn = getPgConn(); diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index 035f5b5a3..d0a32edfc 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -64,6 +64,9 @@ private: void _networksWatcher_Postgres(PGconn *conn); void _networksWatcher_RabbitMQ(); + void _membersWatcher_Reids(); + void _networksWatcher_Redis(); + void commitThread(); void onlineNotificationThread(); diff --git a/ext/central-controller-docker/Dockerfile b/ext/central-controller-docker/Dockerfile index 12984ebda..04d4826c3 100644 --- a/ext/central-controller-docker/Dockerfile +++ b/ext/central-controller-docker/Dockerfile @@ -5,7 +5,7 @@ MAINTAINER Adam Ierymekno , Grant Limberg +Copyright (c) 2010-2011, Pieter Noordhuis + +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. diff --git a/ext/hiredis-vip-0.3.0/Makefile b/ext/hiredis-vip-0.3.0/Makefile new file mode 100644 index 000000000..58494bfc3 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/Makefile @@ -0,0 +1,205 @@ +# Hiredis Makefile +# Copyright (C) 2010-2011 Salvatore Sanfilippo +# Copyright (C) 2010-2011 Pieter Noordhuis +# This file is released under the BSD license, see the COPYING file + +OBJ=net.o hiredis.o sds.o async.o read.o hiarray.o hiutil.o command.o crc16.o adlist.o hircluster.o +EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib +TESTS=hiredis-test +LIBNAME=libhiredis_vip +PKGCONFNAME=hiredis.pc + +HIREDIS_VIP_MAJOR=$(shell grep HIREDIS_VIP_MAJOR hircluster.h | awk '{print $$3}') +HIREDIS_VIP_MINOR=$(shell grep HIREDIS_VIP_MINOR hircluster.h | awk '{print $$3}') +HIREDIS_VIP_PATCH=$(shell grep HIREDIS_VIP_PATCH hircluster.h | awk '{print $$3}') + +# Installation related variables and target +PREFIX?=/usr/local +INCLUDE_PATH?=include/hiredis-vip +LIBRARY_PATH?=lib +PKGCONF_PATH?=pkgconfig +INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH) +INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH) +INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH) + +# redis-server configuration used for testing +REDIS_PORT=56379 +REDIS_SERVER=redis-server +define REDIS_TEST_CONFIG + daemonize yes + pidfile /tmp/hiredis-test-redis.pid + port $(REDIS_PORT) + bind 127.0.0.1 + unixsocket /tmp/hiredis-test-redis.sock +endef +export REDIS_TEST_CONFIG + +# Fallback to gcc when $CC is not in $PATH. +CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc') +OPTIMIZATION?=-O3 +WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings +DEBUG?= -g -ggdb +REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) $(ARCH) +REAL_LDFLAGS=$(LDFLAGS) $(ARCH) + +DYLIBSUFFIX=so +STLIBSUFFIX=a +DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR) +DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR) +DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) +DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) +STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) +STLIB_MAKE_CMD=ar rcs $(STLIBNAME) + +# Platform-specific overrides +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') +ifeq ($(uname_S),SunOS) + REAL_LDFLAGS+= -ldl -lnsl -lsocket + DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) + INSTALL= cp -r +endif +ifeq ($(uname_S),Darwin) + DYLIBSUFFIX=dylib + DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(DYLIBSUFFIX) + DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(DYLIBSUFFIX) + DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) +endif + +all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) + +# Deps (use make dep to generate this) + +adlist.o: adlist.c adlist.h hiutil.h +async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h +command.o: command.c command.h hiredis.h read.h sds.h adlist.h hiutil.h hiarray.h +crc16.o: crc16.c hiutil.h +dict.o: dict.c fmacros.h dict.h +hiarray.o: hiarray.c hiarray.h hiutil.h +hircluster.o: hircluster.c fmacros.h hircluster.h hiredis.h read.h sds.h adlist.h hiarray.h hiutil.h async.h command.h dict.c dict.h +hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h +hiutil.o: hiutil.c hiutil.h +net.o: net.c fmacros.h net.h hiredis.h read.h sds.h +read.o: read.c fmacros.h read.h sds.h +sds.o: sds.c sds.h +test.o: test.c fmacros.h hiredis.h read.h sds.h net.h + +$(DYLIBNAME): $(OBJ) + $(DYLIB_MAKE_CMD) $(OBJ) + +$(STLIBNAME): $(OBJ) + $(STLIB_MAKE_CMD) $(OBJ) + +dynamic: $(DYLIBNAME) +static: $(STLIBNAME) + +# Binaries: +hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) + +hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) + +hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME) + +ifndef AE_DIR +hiredis-example-ae: + @echo "Please specify AE_DIR (e.g. /src)" + @false +else +hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME) +endif + +ifndef LIBUV_DIR +hiredis-example-libuv: + @echo "Please specify LIBUV_DIR (e.g. ../libuv/)" + @false +else +hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread $(STLIBNAME) +endif + +hiredis-example: examples/example.c $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) + +examples: $(EXAMPLES) + +hiredis-test: test.o $(STLIBNAME) + +hiredis-%: %.o $(STLIBNAME) + $(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) + +test: hiredis-test + ./hiredis-test + +check: hiredis-test + @echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) - + $(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \ + ( kill `cat /tmp/hiredis-test-redis.pid` && false ) + kill `cat /tmp/hiredis-test-redis.pid` + +.c.o: + $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< + +clean: + rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov + +dep: + $(CC) -MM *.c + +ifeq ($(uname_S),SunOS) + INSTALL?= cp -r +endif + +INSTALL?= cp -a + +$(PKGCONFNAME): hiredis.h + @echo "Generating $@ for pkgconfig..." + @echo prefix=$(PREFIX) > $@ + @echo exec_prefix=\$${prefix} >> $@ + @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@ + @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@ + @echo >> $@ + @echo Name: hiredis >> $@ + @echo Description: Minimalistic C client library for Redis. >> $@ + @echo Version: $(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(HIREDIS_VIP_PATCH) >> $@ + @echo Libs: -L\$${libdir} -lhiredis >> $@ + @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ + +install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) + mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) + $(INSTALL) hiredis.h async.h read.h sds.h hiutil.h hiarray.h dict.h dict.c adlist.h fmacros.h hircluster.h adapters $(INSTALL_INCLUDE_PATH) + $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) + cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME) + cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MAJOR_NAME) $(DYLIBNAME) + $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) + mkdir -p $(INSTALL_PKGCONF_PATH) + $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH) + +32bit: + @echo "" + @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386" + @echo "" + $(MAKE) CFLAGS="-m32" LDFLAGS="-m32" + +32bit-vars: + $(eval CFLAGS=-m32) + $(eval LDFLAGS=-m32) + +gprof: + $(MAKE) CFLAGS="-pg" LDFLAGS="-pg" + +gcov: + $(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" + +coverage: gcov + make check + mkdir -p tmp/lcov + lcov -d . -c -o tmp/lcov/hiredis.info + genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info + +noopt: + $(MAKE) OPTIMIZATION="" + +.PHONY: all test check clean dep install 32bit gprof gcov noopt diff --git a/ext/hiredis-vip-0.3.0/README.md b/ext/hiredis-vip-0.3.0/README.md new file mode 100644 index 000000000..897419390 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/README.md @@ -0,0 +1,255 @@ + +# HIREDIS-VIP + +Hiredis-vip is a C client library for the [Redis](http://redis.io/) database. + +Hiredis-vip supported redis cluster. + +Hiredis-vip fully contained and based on [Hiredis](https://github.com/redis/hiredis) . + +## CLUSTER SUPPORT + +### FEATURES: + +* **`SUPPORT REDIS CLUSTER`**: + * Connect to redis cluster and run commands. + +* **`SUPPORT MULTI-KEY COMMAND`**: + * Support `MSET`, `MGET` and `DEL`. + +* **`SUPPORT PIPELING`**: + * Support redis pipeline and can contain multi-key command like above. + +* **`SUPPORT Asynchronous API`**: + * User can run commands with asynchronous mode. + +### CLUSTER API: + +```c +redisClusterContext *redisClusterConnect(const char *addrs, int flags); +redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv, int flags); +redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); +void redisClusterFree(redisClusterContext *cc); +void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); +void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len); +void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap); +void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); +void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); +redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); +int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len); +int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap); +int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); +int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); +int redisClusterGetReply(redisClusterContext *cc, void **reply); +void redisClusterReset(redisClusterContext *cc); + +redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); +int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); +int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); +int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len); +int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap); +int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...); +int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); + +void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); +void redisClusterAsyncFree(redisClusterAsyncContext *acc); +``` + +## Quick usage + +If you want used but not read the follow, please reference the examples: +https://github.com/vipshop/hiredis-vip/wiki + +## Cluster synchronous API + +To consume the synchronous API, there are only a few function calls that need to be introduced: + +```c +redisClusterContext *redisClusterConnect(const char *addrs, int flags); +void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); +void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); +void redisClusterFree(redisClusterContext *cc); +``` + +### Cluster connecting + +The function `redisClusterConnect` is used to create a so-called `redisClusterContext`. The +context is where Hiredis-vip Cluster holds state for connections. The `redisClusterContext` +struct has an integer `err` field that is non-zero when the connection is in +an error state. The field `errstr` will contain a string with a description of +the error. +After trying to connect to Redis using `redisClusterContext` you should +check the `err` field to see if establishing the connection was successful: +```c +redisClusterContext *cc = redisClusterConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); +if (cc != NULL && cc->err) { + printf("Error: %s\n", cc->errstr); + // handle error +} +``` + +### Cluster sending commands + +The next that will be introduced is `redisClusterCommand`. +This function takes a format similar to printf. In the simplest form, +it is used like this: +```c +reply = redisClusterCommand(clustercontext, "SET foo bar"); +``` + +The specifier `%s` interpolates a string in the command, and uses `strlen` to +determine the length of the string: +```c +reply = redisClusterCommand(clustercontext, "SET foo %s", value); +``` +Internally, Hiredis-vip splits the command in different arguments and will +convert it to the protocol used to communicate with Redis. +One or more spaces separates arguments, so you can use the specifiers +anywhere in an argument: +```c +reply = redisClusterCommand(clustercontext, "SET key:%s %s", myid, value); +``` + +### Cluster multi-key commands + +Hiredis-vip supports mget/mset/del multi-key commands. +Those multi-key commands is highly effective. +Millions of keys in one mget command just used several seconds. + +Example: +```c +reply = redisClusterCommand(clustercontext, "mget %s %s %s %s", key1, key2, key3, key4); +``` + +### Cluster cleaning up + +To disconnect and free the context the following function can be used: +```c +void redisClusterFree(redisClusterContext *cc); +``` +This function immediately closes the socket and then frees the allocations done in +creating the context. + +### Cluster pipelining + +The function `redisClusterGetReply` is exported as part of the Hiredis API and can be used +when a reply is expected on the socket. To pipeline commands, the only things that needs +to be done is filling up the output buffer. For this cause, two commands can be used that +are identical to the `redisClusterCommand` family, apart from not returning a reply: +```c +int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); +int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv); +``` +After calling either function one or more times, `redisClusterGetReply` can be used to receive the +subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where +the latter means an error occurred while reading a reply. Just as with the other commands, +the `err` field in the context can be used to find out what the cause of this error is. +```c +void redisClusterReset(redisClusterContext *cc); +``` +Warning: You must call `redisClusterReset` function after one pipelining anyway. + +The following examples shows a simple cluster pipeline: +```c +redisReply *reply; +redisClusterAppendCommand(clusterContext,"SET foo bar"); +redisClusterAppendCommand(clusterContext,"GET foo"); +redisClusterGetReply(clusterContext,&reply); // reply for SET +freeReplyObject(reply); +redisClusterGetReply(clusterContext,&reply); // reply for GET +freeReplyObject(reply); +redisClusterReset(clusterContext); +``` + +## Cluster asynchronous API + +Hiredis-vip comes with an cluster asynchronous API that works easily with any event library. +Now we just support and test for libevent and redis ae, if you need for other event libraries, +please contact with us, and we will support it quickly. + +### Connecting + +The function `redisAsyncConnect` can be used to establish a non-blocking connection to +Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field +should be checked after creation to see if there were errors creating the connection. +Because the connection that will be created is non-blocking, the kernel is not able to +instantly return if the specified host and port is able to accept a connection. +```c +redisClusterAsyncContext *acc = redisClusterAsyncConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); +if (acc->err) { + printf("Error: %s\n", acc->errstr); + // handle error +} +``` + +The cluster asynchronous context can hold a disconnect callback function that is called when the +connection is disconnected (either because of an error or per user request). This function should +have the following prototype: +```c +void(const redisAsyncContext *c, int status); +``` +On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the +user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err` +field in the context can be accessed to find out the cause of the error. + +You not need to reconnect in the disconnect callback, hiredis-vip will reconnect this connection itself +when commands come to this redis node. + +Setting the disconnect callback can only be done once per context. For subsequent calls it will +return `REDIS_ERR`. The function to set the disconnect callback has the following prototype: +```c +int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); +``` +### Sending commands and their callbacks + +In an cluster asynchronous context, commands are automatically pipelined due to the nature of an event loop. +Therefore, unlike the cluster synchronous API, there is only a single way to send commands. +Because commands are sent to Redis cluster asynchronously, issuing a command requires a callback function +that is called when the reply is received. Reply callbacks should have the following prototype: +```c +void(redisClusterAsyncContext *acc, void *reply, void *privdata); +``` +The `privdata` argument can be used to curry arbitrary data to the callback from the point where +the command is initially queued for execution. + +The functions that can be used to issue commands in an asynchronous context are: +```c +int redisClusterAsyncCommand( + redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, + void *privdata, const char *format, ...); +``` +This function work like their blocking counterparts. The return value is `REDIS_OK` when the command +was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection +is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is +returned on calls to the `redisClusterAsyncCommand` family. + +If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback +for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only +valid for the duration of the callback. + +All pending callbacks are called with a `NULL` reply when the context encountered an error. + +### Disconnecting + +An cluster asynchronous connection can be terminated using: +```c +void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); +``` +When this function is called, the connection is **not** immediately terminated. Instead, new +commands are no longer accepted and the connection is only terminated when all pending commands +have been written to the socket, their respective replies have been read and their respective +callbacks have been executed. After this, the disconnection callback is executed with the +`REDIS_OK` status and the context object is freed. + +### Hooking it up to event library *X* + +There are a few hooks that need to be set on the cluster context object after it is created. +See the `adapters/` directory for bindings to *ae* and *libevent*. + +## AUTHORS + +Hiredis-vip was maintained and used at vipshop(https://github.com/vipshop). +The redis client library part in hiredis-vip is same as hiredis(https://github.com/redis/hiredis). +The redis cluster client library part in hiredis-vip is written by deep(https://github.com/deep011). +Hiredis-vip is released under the BSD license. diff --git a/ext/hiredis-vip-0.3.0/adapters/ae.h b/ext/hiredis-vip-0.3.0/adapters/ae.h new file mode 100644 index 000000000..f861cf287 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adapters/ae.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + +#ifndef __HIREDIS_AE_H__ +#define __HIREDIS_AE_H__ +#include +#include +#include "../hiredis.h" +#include "../async.h" + +#if 1 //shenzheng 2015-11-5 redis cluster +#include "../hircluster.h" +#endif //shenzheng 2015-11-5 redis cluster + +typedef struct redisAeEvents { + redisAsyncContext *context; + aeEventLoop *loop; + int fd; + int reading, writing; +} redisAeEvents; + +static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { + ((void)el); ((void)fd); ((void)mask); + + redisAeEvents *e = (redisAeEvents*)privdata; + redisAsyncHandleRead(e->context); +} + +static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { + ((void)el); ((void)fd); ((void)mask); + + redisAeEvents *e = (redisAeEvents*)privdata; + redisAsyncHandleWrite(e->context); +} + +static void redisAeAddRead(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (!e->reading) { + e->reading = 1; + aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e); + } +} + +static void redisAeDelRead(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (e->reading) { + e->reading = 0; + aeDeleteFileEvent(loop,e->fd,AE_READABLE); + } +} + +static void redisAeAddWrite(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (!e->writing) { + e->writing = 1; + aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e); + } +} + +static void redisAeDelWrite(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (e->writing) { + e->writing = 0; + aeDeleteFileEvent(loop,e->fd,AE_WRITABLE); + } +} + +static void redisAeCleanup(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + redisAeDelRead(privdata); + redisAeDelWrite(privdata); + free(e); +} + +static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisAeEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisAeEvents*)malloc(sizeof(*e)); + e->context = ac; + e->loop = loop; + e->fd = c->fd; + e->reading = e->writing = 0; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisAeAddRead; + ac->ev.delRead = redisAeDelRead; + ac->ev.addWrite = redisAeAddWrite; + ac->ev.delWrite = redisAeDelWrite; + ac->ev.cleanup = redisAeCleanup; + ac->ev.data = e; + + return REDIS_OK; +} + +#if 1 //shenzheng 2015-11-5 redis cluster + +static int redisAeAttach_link(redisAsyncContext *ac, void *base) +{ + redisAeAttach((aeEventLoop *)base, ac); +} + +static int redisClusterAeAttach(aeEventLoop *loop, redisClusterAsyncContext *acc) { + + if(acc == NULL || loop == NULL) + { + return REDIS_ERR; + } + + acc->adapter = loop; + acc->attach_fn = redisAeAttach_link; + + return REDIS_OK; +} + +#endif //shenzheng 2015-11-5 redis cluster + +#endif diff --git a/ext/hiredis-vip-0.3.0/adapters/glib.h b/ext/hiredis-vip-0.3.0/adapters/glib.h new file mode 100644 index 000000000..e13eee73b --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adapters/glib.h @@ -0,0 +1,153 @@ +#ifndef __HIREDIS_GLIB_H__ +#define __HIREDIS_GLIB_H__ + +#include + +#include "../hiredis.h" +#include "../async.h" + +typedef struct +{ + GSource source; + redisAsyncContext *ac; + GPollFD poll_fd; +} RedisSource; + +static void +redis_source_add_read (gpointer data) +{ + RedisSource *source = data; + g_return_if_fail(source); + source->poll_fd.events |= G_IO_IN; + g_main_context_wakeup(g_source_get_context(data)); +} + +static void +redis_source_del_read (gpointer data) +{ + RedisSource *source = data; + g_return_if_fail(source); + source->poll_fd.events &= ~G_IO_IN; + g_main_context_wakeup(g_source_get_context(data)); +} + +static void +redis_source_add_write (gpointer data) +{ + RedisSource *source = data; + g_return_if_fail(source); + source->poll_fd.events |= G_IO_OUT; + g_main_context_wakeup(g_source_get_context(data)); +} + +static void +redis_source_del_write (gpointer data) +{ + RedisSource *source = data; + g_return_if_fail(source); + source->poll_fd.events &= ~G_IO_OUT; + g_main_context_wakeup(g_source_get_context(data)); +} + +static void +redis_source_cleanup (gpointer data) +{ + RedisSource *source = data; + + g_return_if_fail(source); + + redis_source_del_read(source); + redis_source_del_write(source); + /* + * It is not our responsibility to remove ourself from the + * current main loop. However, we will remove the GPollFD. + */ + if (source->poll_fd.fd >= 0) { + g_source_remove_poll(data, &source->poll_fd); + source->poll_fd.fd = -1; + } +} + +static gboolean +redis_source_prepare (GSource *source, + gint *timeout_) +{ + RedisSource *redis = (RedisSource *)source; + *timeout_ = -1; + return !!(redis->poll_fd.events & redis->poll_fd.revents); +} + +static gboolean +redis_source_check (GSource *source) +{ + RedisSource *redis = (RedisSource *)source; + return !!(redis->poll_fd.events & redis->poll_fd.revents); +} + +static gboolean +redis_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + RedisSource *redis = (RedisSource *)source; + + if ((redis->poll_fd.revents & G_IO_OUT)) { + redisAsyncHandleWrite(redis->ac); + redis->poll_fd.revents &= ~G_IO_OUT; + } + + if ((redis->poll_fd.revents & G_IO_IN)) { + redisAsyncHandleRead(redis->ac); + redis->poll_fd.revents &= ~G_IO_IN; + } + + if (callback) { + return callback(user_data); + } + + return TRUE; +} + +static void +redis_source_finalize (GSource *source) +{ + RedisSource *redis = (RedisSource *)source; + + if (redis->poll_fd.fd >= 0) { + g_source_remove_poll(source, &redis->poll_fd); + redis->poll_fd.fd = -1; + } +} + +static GSource * +redis_source_new (redisAsyncContext *ac) +{ + static GSourceFuncs source_funcs = { + .prepare = redis_source_prepare, + .check = redis_source_check, + .dispatch = redis_source_dispatch, + .finalize = redis_source_finalize, + }; + redisContext *c = &ac->c; + RedisSource *source; + + g_return_val_if_fail(ac != NULL, NULL); + + source = (RedisSource *)g_source_new(&source_funcs, sizeof *source); + source->ac = ac; + source->poll_fd.fd = c->fd; + source->poll_fd.events = 0; + source->poll_fd.revents = 0; + g_source_add_poll((GSource *)source, &source->poll_fd); + + ac->ev.addRead = redis_source_add_read; + ac->ev.delRead = redis_source_del_read; + ac->ev.addWrite = redis_source_add_write; + ac->ev.delWrite = redis_source_del_write; + ac->ev.cleanup = redis_source_cleanup; + ac->ev.data = source; + + return (GSource *)source; +} + +#endif /* __HIREDIS_GLIB_H__ */ diff --git a/ext/hiredis-vip-0.3.0/adapters/libev.h b/ext/hiredis-vip-0.3.0/adapters/libev.h new file mode 100644 index 000000000..2bf8d521f --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adapters/libev.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + +#ifndef __HIREDIS_LIBEV_H__ +#define __HIREDIS_LIBEV_H__ +#include +#include +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisLibevEvents { + redisAsyncContext *context; + struct ev_loop *loop; + int reading, writing; + ev_io rev, wev; +} redisLibevEvents; + +static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { +#if EV_MULTIPLICITY + ((void)loop); +#endif + ((void)revents); + + redisLibevEvents *e = (redisLibevEvents*)watcher->data; + redisAsyncHandleRead(e->context); +} + +static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { +#if EV_MULTIPLICITY + ((void)loop); +#endif + ((void)revents); + + redisLibevEvents *e = (redisLibevEvents*)watcher->data; + redisAsyncHandleWrite(e->context); +} + +static void redisLibevAddRead(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (!e->reading) { + e->reading = 1; + ev_io_start(EV_A_ &e->rev); + } +} + +static void redisLibevDelRead(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (e->reading) { + e->reading = 0; + ev_io_stop(EV_A_ &e->rev); + } +} + +static void redisLibevAddWrite(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (!e->writing) { + e->writing = 1; + ev_io_start(EV_A_ &e->wev); + } +} + +static void redisLibevDelWrite(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (e->writing) { + e->writing = 0; + ev_io_stop(EV_A_ &e->wev); + } +} + +static void redisLibevCleanup(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + redisLibevDelRead(privdata); + redisLibevDelWrite(privdata); + free(e); +} + +static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisLibevEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisLibevEvents*)malloc(sizeof(*e)); + e->context = ac; +#if EV_MULTIPLICITY + e->loop = loop; +#else + e->loop = NULL; +#endif + e->reading = e->writing = 0; + e->rev.data = e; + e->wev.data = e; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisLibevAddRead; + ac->ev.delRead = redisLibevDelRead; + ac->ev.addWrite = redisLibevAddWrite; + ac->ev.delWrite = redisLibevDelWrite; + ac->ev.cleanup = redisLibevCleanup; + ac->ev.data = e; + + /* Initialize read/write events */ + ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ); + ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE); + return REDIS_OK; +} + +#endif diff --git a/ext/hiredis-vip-0.3.0/adapters/libevent.h b/ext/hiredis-vip-0.3.0/adapters/libevent.h new file mode 100644 index 000000000..6bc911c77 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adapters/libevent.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + +#ifndef __HIREDIS_LIBEVENT_H__ +#define __HIREDIS_LIBEVENT_H__ +#include +#include "../hiredis.h" +#include "../async.h" + +#if 1 //shenzheng 2015-9-21 redis cluster +#include "../hircluster.h" +#endif //shenzheng 2015-9-21 redis cluster + +typedef struct redisLibeventEvents { + redisAsyncContext *context; + struct event rev, wev; +} redisLibeventEvents; + +static void redisLibeventReadEvent(int fd, short event, void *arg) { + ((void)fd); ((void)event); + redisLibeventEvents *e = (redisLibeventEvents*)arg; + redisAsyncHandleRead(e->context); +} + +static void redisLibeventWriteEvent(int fd, short event, void *arg) { + ((void)fd); ((void)event); + redisLibeventEvents *e = (redisLibeventEvents*)arg; + redisAsyncHandleWrite(e->context); +} + +static void redisLibeventAddRead(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_add(&e->rev,NULL); +} + +static void redisLibeventDelRead(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_del(&e->rev); +} + +static void redisLibeventAddWrite(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_add(&e->wev,NULL); +} + +static void redisLibeventDelWrite(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_del(&e->wev); +} + +static void redisLibeventCleanup(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_del(&e->rev); + event_del(&e->wev); + free(e); +} + +static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { + redisContext *c = &(ac->c); + redisLibeventEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisLibeventEvents*)malloc(sizeof(*e)); + e->context = ac; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisLibeventAddRead; + ac->ev.delRead = redisLibeventDelRead; + ac->ev.addWrite = redisLibeventAddWrite; + ac->ev.delWrite = redisLibeventDelWrite; + ac->ev.cleanup = redisLibeventCleanup; + ac->ev.data = e; + + /* Initialize and install read/write events */ + event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e); + event_set(&e->wev,c->fd,EV_WRITE,redisLibeventWriteEvent,e); + event_base_set(base,&e->rev); + event_base_set(base,&e->wev); + return REDIS_OK; +} + +#if 1 //shenzheng 2015-9-21 redis cluster + +static int redisLibeventAttach_link(redisAsyncContext *ac, void *base) +{ + redisLibeventAttach(ac, (struct event_base *)base); +} + +static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct event_base *base) { + + if(acc == NULL || base == NULL) + { + return REDIS_ERR; + } + + acc->adapter = base; + acc->attach_fn = redisLibeventAttach_link; + + return REDIS_OK; +} + +#endif //shenzheng 2015-9-21 redis cluster + +#endif diff --git a/ext/hiredis-vip-0.3.0/adapters/libuv.h b/ext/hiredis-vip-0.3.0/adapters/libuv.h new file mode 100644 index 000000000..3c9a49f53 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adapters/libuv.h @@ -0,0 +1,122 @@ +#ifndef __HIREDIS_LIBUV_H__ +#define __HIREDIS_LIBUV_H__ +#include +#include +#include "../hiredis.h" +#include "../async.h" +#include + +typedef struct redisLibuvEvents { + redisAsyncContext* context; + uv_poll_t handle; + int events; +} redisLibuvEvents; + + +static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + + if (status != 0) { + return; + } + + if (events & UV_READABLE) { + redisAsyncHandleRead(p->context); + } + if (events & UV_WRITABLE) { + redisAsyncHandleWrite(p->context); + } +} + + +static void redisLibuvAddRead(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events |= UV_READABLE; + + uv_poll_start(&p->handle, p->events, redisLibuvPoll); +} + + +static void redisLibuvDelRead(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events &= ~UV_READABLE; + + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } +} + + +static void redisLibuvAddWrite(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events |= UV_WRITABLE; + + uv_poll_start(&p->handle, p->events, redisLibuvPoll); +} + + +static void redisLibuvDelWrite(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events &= ~UV_WRITABLE; + + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } +} + + +static void on_close(uv_handle_t* handle) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + + free(p); +} + + +static void redisLibuvCleanup(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + uv_close((uv_handle_t*)&p->handle, on_close); +} + + +static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { + redisContext *c = &(ac->c); + + if (ac->ev.data != NULL) { + return REDIS_ERR; + } + + ac->ev.addRead = redisLibuvAddRead; + ac->ev.delRead = redisLibuvDelRead; + ac->ev.addWrite = redisLibuvAddWrite; + ac->ev.delWrite = redisLibuvDelWrite; + ac->ev.cleanup = redisLibuvCleanup; + + redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p)); + + if (!p) { + return REDIS_ERR; + } + + memset(p, 0, sizeof(*p)); + + if (uv_poll_init(loop, &p->handle, c->fd) != 0) { + return REDIS_ERR; + } + + ac->ev.data = p; + p->handle.data = p; + p->context = ac; + + return REDIS_OK; +} + +#endif diff --git a/ext/hiredis-vip-0.3.0/adlist.c b/ext/hiredis-vip-0.3.0/adlist.c new file mode 100644 index 000000000..b490a6bd1 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adlist.c @@ -0,0 +1,341 @@ +/* adlist.c - A generic doubly linked list implementation + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * 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 +#include "adlist.h" +#include "hiutil.h" + +/* Create a new list. The created list can be freed with + * AlFreeList(), but private value of every node need to be freed + * by the user before to call AlFreeList(). + * + * On error, NULL is returned. Otherwise the pointer to the new list. */ +hilist *listCreate(void) +{ + struct hilist *list; + + if ((list = hi_alloc(sizeof(*list))) == NULL) + return NULL; + list->head = list->tail = NULL; + list->len = 0; + list->dup = NULL; + list->free = NULL; + list->match = NULL; + return list; +} + +/* Free the whole list. + * + * This function can't fail. */ +void listRelease(hilist *list) +{ + unsigned long len; + listNode *current, *next; + + current = list->head; + len = list->len; + while(len--) { + next = current->next; + if (list->free) list->free(current->value); + hi_free(current); + current = next; + } + hi_free(list); +} + +/* Add a new node to the list, to head, containing the specified 'value' + * pointer as value. + * + * On error, NULL is returned and no operation is performed (i.e. the + * list remains unaltered). + * On success the 'list' pointer you pass to the function is returned. */ +hilist *listAddNodeHead(hilist *list, void *value) +{ + listNode *node; + + if ((node = hi_alloc(sizeof(*node))) == NULL) + return NULL; + node->value = value; + if (list->len == 0) { + list->head = list->tail = node; + node->prev = node->next = NULL; + } else { + node->prev = NULL; + node->next = list->head; + list->head->prev = node; + list->head = node; + } + list->len++; + return list; +} + +/* Add a new node to the list, to tail, containing the specified 'value' + * pointer as value. + * + * On error, NULL is returned and no operation is performed (i.e. the + * list remains unaltered). + * On success the 'list' pointer you pass to the function is returned. */ +hilist *listAddNodeTail(hilist *list, void *value) +{ + listNode *node; + + if ((node = hi_alloc(sizeof(*node))) == NULL) + return NULL; + node->value = value; + if (list->len == 0) { + list->head = list->tail = node; + node->prev = node->next = NULL; + } else { + node->prev = list->tail; + node->next = NULL; + list->tail->next = node; + list->tail = node; + } + list->len++; + return list; +} + +hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after) { + listNode *node; + + if ((node = hi_alloc(sizeof(*node))) == NULL) + return NULL; + node->value = value; + if (after) { + node->prev = old_node; + node->next = old_node->next; + if (list->tail == old_node) { + list->tail = node; + } + } else { + node->next = old_node; + node->prev = old_node->prev; + if (list->head == old_node) { + list->head = node; + } + } + if (node->prev != NULL) { + node->prev->next = node; + } + if (node->next != NULL) { + node->next->prev = node; + } + list->len++; + return list; +} + +/* Remove the specified node from the specified list. + * It's up to the caller to free the private value of the node. + * + * This function can't fail. */ +void listDelNode(hilist *list, listNode *node) +{ + if (node->prev) + node->prev->next = node->next; + else + list->head = node->next; + if (node->next) + node->next->prev = node->prev; + else + list->tail = node->prev; + if (list->free) list->free(node->value); + hi_free(node); + list->len--; +} + +/* Returns a list iterator 'iter'. After the initialization every + * call to listNext() will return the next element of the list. + * + * This function can't fail. */ +listIter *listGetIterator(hilist *list, int direction) +{ + listIter *iter; + + if ((iter = hi_alloc(sizeof(*iter))) == NULL) return NULL; + if (direction == AL_START_HEAD) + iter->next = list->head; + else + iter->next = list->tail; + iter->direction = direction; + return iter; +} + +/* Release the iterator memory */ +void listReleaseIterator(listIter *iter) { + hi_free(iter); +} + +/* Create an iterator in the list private iterator structure */ +void listRewind(hilist *list, listIter *li) { + li->next = list->head; + li->direction = AL_START_HEAD; +} + +void listRewindTail(hilist *list, listIter *li) { + li->next = list->tail; + li->direction = AL_START_TAIL; +} + +/* Return the next element of an iterator. + * It's valid to remove the currently returned element using + * listDelNode(), but not to remove other elements. + * + * The function returns a pointer to the next element of the list, + * or NULL if there are no more elements, so the classical usage patter + * is: + * + * iter = listGetIterator(list,); + * while ((node = listNext(iter)) != NULL) { + * doSomethingWith(listNodeValue(node)); + * } + * + * */ +listNode *listNext(listIter *iter) +{ + listNode *current = iter->next; + + if (current != NULL) { + if (iter->direction == AL_START_HEAD) + iter->next = current->next; + else + iter->next = current->prev; + } + return current; +} + +/* Duplicate the whole list. On out of memory NULL is returned. + * On success a copy of the original list is returned. + * + * The 'Dup' method set with listSetDupMethod() function is used + * to copy the node value. Otherwise the same pointer value of + * the original node is used as value of the copied node. + * + * The original list both on success or error is never modified. */ +hilist *listDup(hilist *orig) +{ + hilist *copy; + listIter *iter; + listNode *node; + + if ((copy = listCreate()) == NULL) + return NULL; + copy->dup = orig->dup; + copy->free = orig->free; + copy->match = orig->match; + iter = listGetIterator(orig, AL_START_HEAD); + while((node = listNext(iter)) != NULL) { + void *value; + + if (copy->dup) { + value = copy->dup(node->value); + if (value == NULL) { + listRelease(copy); + listReleaseIterator(iter); + return NULL; + } + } else + value = node->value; + if (listAddNodeTail(copy, value) == NULL) { + listRelease(copy); + listReleaseIterator(iter); + return NULL; + } + } + listReleaseIterator(iter); + return copy; +} + +/* Search the list for a node matching a given key. + * The match is performed using the 'match' method + * set with listSetMatchMethod(). If no 'match' method + * is set, the 'value' pointer of every node is directly + * compared with the 'key' pointer. + * + * On success the first matching node pointer is returned + * (search starts from head). If no matching node exists + * NULL is returned. */ +listNode *listSearchKey(hilist *list, void *key) +{ + listIter *iter; + listNode *node; + + iter = listGetIterator(list, AL_START_HEAD); + while((node = listNext(iter)) != NULL) { + if (list->match) { + if (list->match(node->value, key)) { + listReleaseIterator(iter); + return node; + } + } else { + if (key == node->value) { + listReleaseIterator(iter); + return node; + } + } + } + listReleaseIterator(iter); + return NULL; +} + +/* Return the element at the specified zero-based index + * where 0 is the head, 1 is the element next to head + * and so on. Negative integers are used in order to count + * from the tail, -1 is the last element, -2 the penultimate + * and so on. If the index is out of range NULL is returned. */ +listNode *listIndex(hilist *list, long index) { + listNode *n; + + if (index < 0) { + index = (-index)-1; + n = list->tail; + while(index-- && n) n = n->prev; + } else { + n = list->head; + while(index-- && n) n = n->next; + } + return n; +} + +/* Rotate the list removing the tail node and inserting it to the head. */ +void listRotate(hilist *list) { + listNode *tail = list->tail; + + if (listLength(list) <= 1) return; + + /* Detach current tail */ + list->tail = tail->prev; + list->tail->next = NULL; + /* Move it as head */ + list->head->prev = tail; + tail->prev = NULL; + tail->next = list->head; + list->head = tail; +} diff --git a/ext/hiredis-vip-0.3.0/adlist.h b/ext/hiredis-vip-0.3.0/adlist.h new file mode 100644 index 000000000..5b9a53ea5 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adlist.h @@ -0,0 +1,93 @@ +/* adlist.h - A generic doubly linked list implementation + * + * Copyright (c) 2006-2012, Salvatore Sanfilippo + * 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. + */ + +#ifndef __ADLIST_H__ +#define __ADLIST_H__ + +/* Node, List, and Iterator are the only data structures used currently. */ + +typedef struct listNode { + struct listNode *prev; + struct listNode *next; + void *value; +} listNode; + +typedef struct listIter { + listNode *next; + int direction; +} listIter; + +typedef struct hilist { + listNode *head; + listNode *tail; + void *(*dup)(void *ptr); + void (*free)(void *ptr); + int (*match)(void *ptr, void *key); + unsigned long len; +} hilist; + +/* Functions implemented as macros */ +#define listLength(l) ((l)->len) +#define listFirst(l) ((l)->head) +#define listLast(l) ((l)->tail) +#define listPrevNode(n) ((n)->prev) +#define listNextNode(n) ((n)->next) +#define listNodeValue(n) ((n)->value) + +#define listSetDupMethod(l,m) ((l)->dup = (m)) +#define listSetFreeMethod(l,m) ((l)->free = (m)) +#define listSetMatchMethod(l,m) ((l)->match = (m)) + +#define listGetDupMethod(l) ((l)->dup) +#define listGetFree(l) ((l)->free) +#define listGetMatchMethod(l) ((l)->match) + +/* Prototypes */ +hilist *listCreate(void); +void listRelease(hilist *list); +hilist *listAddNodeHead(hilist *list, void *value); +hilist *listAddNodeTail(hilist *list, void *value); +hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after); +void listDelNode(hilist *list, listNode *node); +listIter *listGetIterator(hilist *list, int direction); +listNode *listNext(listIter *iter); +void listReleaseIterator(listIter *iter); +hilist *listDup(hilist *orig); +listNode *listSearchKey(hilist *list, void *key); +listNode *listIndex(hilist *list, long index); +void listRewind(hilist *list, listIter *li); +void listRewindTail(hilist *list, listIter *li); +void listRotate(hilist *list); + +/* Directions for iterators */ +#define AL_START_HEAD 0 +#define AL_START_TAIL 1 + +#endif /* __ADLIST_H__ */ diff --git a/ext/hiredis-vip-0.3.0/async.c b/ext/hiredis-vip-0.3.0/async.c new file mode 100644 index 000000000..75a3575de --- /dev/null +++ b/ext/hiredis-vip-0.3.0/async.c @@ -0,0 +1,691 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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 "fmacros.h" +#include +#include +#include +#include +#include +#include +#include "async.h" +#include "net.h" +#include "dict.c" +#include "sds.h" + +#define _EL_ADD_READ(ctx) do { \ + if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_READ(ctx) do { \ + if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ + } while(0) +#define _EL_ADD_WRITE(ctx) do { \ + if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_WRITE(ctx) do { \ + if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ + } while(0) +#define _EL_CLEANUP(ctx) do { \ + if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ + } while(0); + +/* Forward declaration of function in hiredis.c */ +int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); + +/* Functions managing dictionary of callbacks for pub/sub. */ +static unsigned int callbackHash(const void *key) { + return dictGenHashFunction((const unsigned char *)key, + sdslen((const sds)key)); +} + +static void *callbackValDup(void *privdata, const void *src) { + ((void) privdata); + redisCallback *dup = malloc(sizeof(*dup)); + memcpy(dup,src,sizeof(*dup)); + return dup; +} + +static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { + int l1, l2; + ((void) privdata); + + l1 = sdslen((const sds)key1); + l2 = sdslen((const sds)key2); + if (l1 != l2) return 0; + return memcmp(key1,key2,l1) == 0; +} + +static void callbackKeyDestructor(void *privdata, void *key) { + ((void) privdata); + sdsfree((sds)key); +} + +static void callbackValDestructor(void *privdata, void *val) { + ((void) privdata); + free(val); +} + +static dictType callbackDict = { + callbackHash, + NULL, + callbackValDup, + callbackKeyCompare, + callbackKeyDestructor, + callbackValDestructor +}; + +static redisAsyncContext *redisAsyncInitialize(redisContext *c) { + redisAsyncContext *ac; + + ac = realloc(c,sizeof(redisAsyncContext)); + if (ac == NULL) + return NULL; + + c = &(ac->c); + + /* The regular connect functions will always set the flag REDIS_CONNECTED. + * For the async API, we want to wait until the first write event is + * received up before setting this flag, so reset it here. */ + c->flags &= ~REDIS_CONNECTED; + + ac->err = 0; + ac->errstr = NULL; + ac->data = NULL; + ac->dataHandler = NULL; + + ac->ev.data = NULL; + ac->ev.addRead = NULL; + ac->ev.delRead = NULL; + ac->ev.addWrite = NULL; + ac->ev.delWrite = NULL; + ac->ev.cleanup = NULL; + + ac->onConnect = NULL; + ac->onDisconnect = NULL; + + ac->replies.head = NULL; + ac->replies.tail = NULL; + ac->sub.invalid.head = NULL; + ac->sub.invalid.tail = NULL; + ac->sub.channels = dictCreate(&callbackDict,NULL); + ac->sub.patterns = dictCreate(&callbackDict,NULL); + return ac; +} + +/* We want the error field to be accessible directly instead of requiring + * an indirection to the redisContext struct. */ +static void __redisAsyncCopyError(redisAsyncContext *ac) { + if (!ac) + return; + + redisContext *c = &(ac->c); + ac->err = c->err; + ac->errstr = c->errstr; +} + +redisAsyncContext *redisAsyncConnect(const char *ip, int port) { + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectNonBlock(ip,port); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); + if (ac == NULL) { + redisFree(c); + return NULL; + } + + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); + redisAsyncContext *ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); + redisAsyncContext *ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectUnix(const char *path) { + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectUnixNonBlock(path); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); + if (ac == NULL) { + redisFree(c); + return NULL; + } + + __redisAsyncCopyError(ac); + return ac; +} + +int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { + if (ac->onConnect == NULL) { + ac->onConnect = fn; + + /* The common way to detect an established connection is to wait for + * the first write event to be fired. This assumes the related event + * library functions are already set. */ + _EL_ADD_WRITE(ac); + return REDIS_OK; + } + return REDIS_ERR; +} + +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { + if (ac->onDisconnect == NULL) { + ac->onDisconnect = fn; + return REDIS_OK; + } + return REDIS_ERR; +} + +/* Helper functions to push/shift callbacks */ +static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { + redisCallback *cb; + + /* Copy callback from stack to heap */ + cb = malloc(sizeof(*cb)); + if (cb == NULL) + return REDIS_ERR_OOM; + + if (source != NULL) { + memcpy(cb,source,sizeof(*cb)); + cb->next = NULL; + } + + /* Store callback in list */ + if (list->head == NULL) + list->head = cb; + if (list->tail != NULL) + list->tail->next = cb; + list->tail = cb; + return REDIS_OK; +} + +static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { + redisCallback *cb = list->head; + if (cb != NULL) { + list->head = cb->next; + if (cb == list->tail) + list->tail = NULL; + + /* Copy callback from heap to stack */ + if (target != NULL) + memcpy(target,cb,sizeof(*cb)); + free(cb); + return REDIS_OK; + } + return REDIS_ERR; +} + +static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { + redisContext *c = &(ac->c); + if (cb->fn != NULL) { + c->flags |= REDIS_IN_CALLBACK; + cb->fn(ac,reply,cb->privdata); + c->flags &= ~REDIS_IN_CALLBACK; + } +} + +/* Helper function to free the context. */ +static void __redisAsyncFree(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb; + dictIterator *it; + dictEntry *de; + + /* Execute pending callbacks with NULL reply. */ + while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) + __redisRunCallback(ac,&cb,NULL); + + /* Execute callbacks for invalid commands */ + while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) + __redisRunCallback(ac,&cb,NULL); + + /* Run subscription callbacks callbacks with NULL reply */ + it = dictGetIterator(ac->sub.channels); + while ((de = dictNext(it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); + dictReleaseIterator(it); + dictRelease(ac->sub.channels); + + it = dictGetIterator(ac->sub.patterns); + while ((de = dictNext(it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); + dictReleaseIterator(it); + dictRelease(ac->sub.patterns); + + /* Signal event lib to clean up */ + _EL_CLEANUP(ac); + + /* Execute disconnect callback. When redisAsyncFree() initiated destroying + * this context, the status will always be REDIS_OK. */ + if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { + if (c->flags & REDIS_FREEING) { + ac->onDisconnect(ac,REDIS_OK); + } else { + ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); + } + } + + if (ac->dataHandler) { + ac->dataHandler(ac); + } + + /* Cleanup self */ + redisFree(c); +} + +/* Free the async context. When this function is called from a callback, + * control needs to be returned to redisProcessCallbacks() before actual + * free'ing. To do so, a flag is set on the context which is picked up by + * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ +void redisAsyncFree(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + c->flags |= REDIS_FREEING; + if (!(c->flags & REDIS_IN_CALLBACK)) + __redisAsyncFree(ac); +} + +/* Helper function to make the disconnect happen and clean up. */ +static void __redisAsyncDisconnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + /* Make sure error is accessible if there is any */ + __redisAsyncCopyError(ac); + + if (ac->err == 0) { + /* For clean disconnects, there should be no pending callbacks. */ + assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR); + } else { + /* Disconnection is caused by an error, make sure that pending + * callbacks cannot call new commands. */ + c->flags |= REDIS_DISCONNECTING; + } + + /* For non-clean disconnects, __redisAsyncFree() will execute pending + * callbacks with a NULL-reply. */ + __redisAsyncFree(ac); +} + +/* Tries to do a clean disconnect from Redis, meaning it stops new commands + * from being issued, but tries to flush the output buffer and execute + * callbacks for all remaining replies. When this function is called from a + * callback, there might be more replies and we can safely defer disconnecting + * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately + * when there are no pending callbacks. */ +void redisAsyncDisconnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + c->flags |= REDIS_DISCONNECTING; + if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) + __redisAsyncDisconnect(ac); +} + +static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { + redisContext *c = &(ac->c); + dict *callbacks; + dictEntry *de; + int pvariant; + char *stype; + sds sname; + + /* Custom reply functions are not supported for pub/sub. This will fail + * very hard when they are used... */ + if (reply->type == REDIS_REPLY_ARRAY) { + assert(reply->elements >= 2); + assert(reply->element[0]->type == REDIS_REPLY_STRING); + stype = reply->element[0]->str; + pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; + + if (pvariant) + callbacks = ac->sub.patterns; + else + callbacks = ac->sub.channels; + + /* Locate the right callback */ + assert(reply->element[1]->type == REDIS_REPLY_STRING); + sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); + de = dictFind(callbacks,sname); + if (de != NULL) { + memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb)); + + /* If this is an unsubscribe message, remove it. */ + if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { + dictDelete(callbacks,sname); + + /* If this was the last unsubscribe message, revert to + * non-subscribe mode. */ + assert(reply->element[2]->type == REDIS_REPLY_INTEGER); + if (reply->element[2]->integer == 0) + c->flags &= ~REDIS_SUBSCRIBED; + } + } + sdsfree(sname); + } else { + /* Shift callback for invalid commands. */ + __redisShiftCallback(&ac->sub.invalid,dstcb); + } + return REDIS_OK; +} + +void redisProcessCallbacks(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb = {NULL, NULL, NULL}; + void *reply = NULL; + int status; + + while((status = redisGetReply(c,&reply)) == REDIS_OK) { + if (reply == NULL) { + /* When the connection is being disconnected and there are + * no more replies, this is the cue to really disconnect. */ + if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) { + __redisAsyncDisconnect(ac); + return; + } + + /* If monitor mode, repush callback */ + if(c->flags & REDIS_MONITORING) { + __redisPushCallback(&ac->replies,&cb); + } + + /* When the connection is not being disconnected, simply stop + * trying to get replies and wait for the next loop tick. */ + break; + } + + /* Even if the context is subscribed, pending regular callbacks will + * get a reply before pub/sub messages arrive. */ + if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { + /* + * A spontaneous reply in a not-subscribed context can be the error + * reply that is sent when a new connection exceeds the maximum + * number of allowed connections on the server side. + * + * This is seen as an error instead of a regular reply because the + * server closes the connection after sending it. + * + * To prevent the error from being overwritten by an EOF error the + * connection is closed here. See issue #43. + * + * Another possibility is that the server is loading its dataset. + * In this case we also want to close the connection, and have the + * user wait until the server is ready to take our request. + */ + if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { + c->err = REDIS_ERR_OTHER; + snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); + c->reader->fn->freeObject(reply); + __redisAsyncDisconnect(ac); + return; + } + /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ + assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); + if(c->flags & REDIS_SUBSCRIBED) + __redisGetSubscribeCallback(ac,reply,&cb); + } + + if (cb.fn != NULL) { + __redisRunCallback(ac,&cb,reply); + c->reader->fn->freeObject(reply); + + /* Proceed with free'ing when redisAsyncFree() was called. */ + if (c->flags & REDIS_FREEING) { + __redisAsyncFree(ac); + return; + } + } else { + /* No callback for this reply. This can either be a NULL callback, + * or there were no callbacks to begin with. Either way, don't + * abort with an error, but simply ignore it because the client + * doesn't know what the server will spit out over the wire. */ + c->reader->fn->freeObject(reply); + } + } + + /* Disconnect when there was an error reading the reply */ + if (status != REDIS_OK) + __redisAsyncDisconnect(ac); +} + +/* Internal helper function to detect socket status the first time a read or + * write event fires. When connecting was not succesful, the connect callback + * is called with a REDIS_ERR status and the context is free'd. */ +static int __redisAsyncHandleConnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (redisCheckSocketError(c) == REDIS_ERR) { + /* Try again later when connect(2) is still in progress. */ + if (errno == EINPROGRESS) + return REDIS_OK; + + if (ac->onConnect) ac->onConnect(ac,REDIS_ERR); + __redisAsyncDisconnect(ac); + return REDIS_ERR; + } + + /* Mark context as connected. */ + c->flags |= REDIS_CONNECTED; + if (ac->onConnect) ac->onConnect(ac,REDIS_OK); + return REDIS_OK; +} + +/* This function should be called when the socket is readable. + * It processes all replies that can be read and executes their callbacks. + */ +void redisAsyncHandleRead(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + + if (redisBufferRead(c) == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + /* Always re-schedule reads */ + _EL_ADD_READ(ac); + redisProcessCallbacks(ac); + } +} + +void redisAsyncHandleWrite(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + int done = 0; + + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + + if (redisBufferWrite(c,&done) == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + /* Continue writing when not done, stop writing otherwise */ + if (!done) + _EL_ADD_WRITE(ac); + else + _EL_DEL_WRITE(ac); + + /* Always schedule reads after writes */ + _EL_ADD_READ(ac); + } +} + +/* Sets a pointer to the first argument and its length starting at p. Returns + * the number of bytes to skip to get to the following argument. */ +static const char *nextArgument(const char *start, const char **str, size_t *len) { + const char *p = start; + if (p[0] != '$') { + p = strchr(p,'$'); + if (p == NULL) return NULL; + } + + *len = (int)strtol(p+1,NULL,10); + p = strchr(p,'\r'); + assert(p); + *str = p+2; + return p+2+(*len)+2; +} + +/* Helper function for the redisAsyncCommand* family of functions. Writes a + * formatted command to the output buffer and registers the provided callback + * function with the context. */ +static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { + redisContext *c = &(ac->c); + redisCallback cb; + int pvariant, hasnext; + const char *cstr, *astr; + size_t clen, alen; + const char *p; + sds sname; + int ret; + + /* Don't accept new commands when the connection is about to be closed. */ + if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; + + /* Setup callback */ + cb.fn = fn; + cb.privdata = privdata; + + /* Find out which command will be appended. */ + p = nextArgument(cmd,&cstr,&clen); + assert(p != NULL); + hasnext = (p[0] == '$'); + pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; + cstr += pvariant; + clen -= pvariant; + + if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { + c->flags |= REDIS_SUBSCRIBED; + + /* Add every channel/pattern to the list of subscription callbacks. */ + while ((p = nextArgument(p,&astr,&alen)) != NULL) { + sname = sdsnewlen(astr,alen); + if (pvariant) + ret = dictReplace(ac->sub.patterns,sname,&cb); + else + ret = dictReplace(ac->sub.channels,sname,&cb); + + if (ret == 0) sdsfree(sname); + } + } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { + /* It is only useful to call (P)UNSUBSCRIBE when the context is + * subscribed to one or more channels or patterns. */ + if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; + + /* (P)UNSUBSCRIBE does not have its own response: every channel or + * pattern that is unsubscribed will receive a message. This means we + * should not append a callback function for this command. */ + } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) { + /* Set monitor flag and push callback */ + c->flags |= REDIS_MONITORING; + __redisPushCallback(&ac->replies,&cb); + } else { + if (c->flags & REDIS_SUBSCRIBED) + /* This will likely result in an error reply, but it needs to be + * received and passed to the callback. */ + __redisPushCallback(&ac->sub.invalid,&cb); + else + __redisPushCallback(&ac->replies,&cb); + } + + __redisAppendCommand(c,cmd,len); + + /* Always schedule a write when the write buffer is non-empty */ + _EL_ADD_WRITE(ac); + + return REDIS_OK; +} + +int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { + char *cmd; + int len; + int status; + len = redisvFormatCommand(&cmd,format,ap); + + /* We don't want to pass -1 or -2 to future functions as a length. */ + if (len < 0) + return REDIS_ERR; + + status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + free(cmd); + return status; +} + +int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { + va_list ap; + int status; + va_start(ap,format); + status = redisvAsyncCommand(ac,fn,privdata,format,ap); + va_end(ap); + return status; +} + +int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { + sds cmd; + int len; + int status; + len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); + status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + sdsfree(cmd); + return status; +} + +int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { + int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + return status; +} diff --git a/ext/hiredis-vip-0.3.0/async.h b/ext/hiredis-vip-0.3.0/async.h new file mode 100644 index 000000000..2ba7142b8 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/async.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + +#ifndef __HIREDIS_ASYNC_H +#define __HIREDIS_ASYNC_H +#include "hiredis.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct redisAsyncContext; /* need forward declaration of redisAsyncContext */ +struct dict; /* dictionary header is included in async.c */ + +/* Reply callback prototype and container */ +typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); +typedef struct redisCallback { + struct redisCallback *next; /* simple singly linked list */ + redisCallbackFn *fn; + void *privdata; +} redisCallback; + +/* List of callbacks for either regular replies or pub/sub */ +typedef struct redisCallbackList { + redisCallback *head, *tail; +} redisCallbackList; + +/* Connection callback prototypes */ +typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); +typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); + +/* Context for an async connection to Redis */ +typedef struct redisAsyncContext { + /* Hold the regular context, so it can be realloc'ed. */ + redisContext c; + + /* Setup error flags so they can be used directly. */ + int err; + char *errstr; + + /* Not used by hiredis */ + void *data; + void (*dataHandler)(struct redisAsyncContext* ac); + + /* Event library data and hooks */ + struct { + void *data; + + /* Hooks that are called when the library expects to start + * reading/writing. These functions should be idempotent. */ + void (*addRead)(void *privdata); + void (*delRead)(void *privdata); + void (*addWrite)(void *privdata); + void (*delWrite)(void *privdata); + void (*cleanup)(void *privdata); + } ev; + + /* Called when either the connection is terminated due to an error or per + * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ + redisDisconnectCallback *onDisconnect; + + /* Called when the first write event was received. */ + redisConnectCallback *onConnect; + + /* Regular command callbacks */ + redisCallbackList replies; + + /* Subscription callbacks */ + struct { + redisCallbackList invalid; + struct dict *channels; + struct dict *patterns; + } sub; +} redisAsyncContext; + +/* Functions that proxy to hiredis */ +redisAsyncContext *redisAsyncConnect(const char *ip, int port); +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); +redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, + const char *source_addr); +redisAsyncContext *redisAsyncConnectUnix(const char *path); +int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); +void redisAsyncDisconnect(redisAsyncContext *ac); +void redisAsyncFree(redisAsyncContext *ac); + +/* Handle read/write events */ +void redisAsyncHandleRead(redisAsyncContext *ac); +void redisAsyncHandleWrite(redisAsyncContext *ac); + +/* Command functions for an async context. Write the command to the + * output buffer and register the provided callback. */ +int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); +int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); +int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); +int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-vip-0.3.0/command.c b/ext/hiredis-vip-0.3.0/command.c new file mode 100644 index 000000000..e32091b40 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/command.c @@ -0,0 +1,1700 @@ +#include +#include + +#include "command.h" +#include "hiutil.h" +#include "hiarray.h" + + +static uint64_t cmd_id = 0; /* command id counter */ + + +/* + * Return true, if the redis command take no key, otherwise + * return false + */ +static int +redis_argz(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_PING: + case CMD_REQ_REDIS_QUIT: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command accepts no arguments, otherwise + * return false + */ +static int +redis_arg0(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_EXISTS: + case CMD_REQ_REDIS_PERSIST: + case CMD_REQ_REDIS_PTTL: + case CMD_REQ_REDIS_SORT: + case CMD_REQ_REDIS_TTL: + case CMD_REQ_REDIS_TYPE: + case CMD_REQ_REDIS_DUMP: + + case CMD_REQ_REDIS_DECR: + case CMD_REQ_REDIS_GET: + case CMD_REQ_REDIS_INCR: + case CMD_REQ_REDIS_STRLEN: + + case CMD_REQ_REDIS_HGETALL: + case CMD_REQ_REDIS_HKEYS: + case CMD_REQ_REDIS_HLEN: + case CMD_REQ_REDIS_HVALS: + + case CMD_REQ_REDIS_LLEN: + case CMD_REQ_REDIS_LPOP: + case CMD_REQ_REDIS_RPOP: + + case CMD_REQ_REDIS_SCARD: + case CMD_REQ_REDIS_SMEMBERS: + case CMD_REQ_REDIS_SPOP: + + case CMD_REQ_REDIS_ZCARD: + case CMD_REQ_REDIS_PFCOUNT: + case CMD_REQ_REDIS_AUTH: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command accepts exactly 1 argument, otherwise + * return false + */ +static int +redis_arg1(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_EXPIRE: + case CMD_REQ_REDIS_EXPIREAT: + case CMD_REQ_REDIS_PEXPIRE: + case CMD_REQ_REDIS_PEXPIREAT: + + case CMD_REQ_REDIS_APPEND: + case CMD_REQ_REDIS_DECRBY: + case CMD_REQ_REDIS_GETBIT: + case CMD_REQ_REDIS_GETSET: + case CMD_REQ_REDIS_INCRBY: + case CMD_REQ_REDIS_INCRBYFLOAT: + case CMD_REQ_REDIS_SETNX: + + case CMD_REQ_REDIS_HEXISTS: + case CMD_REQ_REDIS_HGET: + + case CMD_REQ_REDIS_LINDEX: + case CMD_REQ_REDIS_LPUSHX: + case CMD_REQ_REDIS_RPOPLPUSH: + case CMD_REQ_REDIS_RPUSHX: + + case CMD_REQ_REDIS_SISMEMBER: + + case CMD_REQ_REDIS_ZRANK: + case CMD_REQ_REDIS_ZREVRANK: + case CMD_REQ_REDIS_ZSCORE: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command accepts exactly 2 arguments, otherwise + * return false + */ +static int +redis_arg2(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_GETRANGE: + case CMD_REQ_REDIS_PSETEX: + case CMD_REQ_REDIS_SETBIT: + case CMD_REQ_REDIS_SETEX: + case CMD_REQ_REDIS_SETRANGE: + + case CMD_REQ_REDIS_HINCRBY: + case CMD_REQ_REDIS_HINCRBYFLOAT: + case CMD_REQ_REDIS_HSET: + case CMD_REQ_REDIS_HSETNX: + + case CMD_REQ_REDIS_LRANGE: + case CMD_REQ_REDIS_LREM: + case CMD_REQ_REDIS_LSET: + case CMD_REQ_REDIS_LTRIM: + + case CMD_REQ_REDIS_SMOVE: + + case CMD_REQ_REDIS_ZCOUNT: + case CMD_REQ_REDIS_ZLEXCOUNT: + case CMD_REQ_REDIS_ZINCRBY: + case CMD_REQ_REDIS_ZREMRANGEBYLEX: + case CMD_REQ_REDIS_ZREMRANGEBYRANK: + case CMD_REQ_REDIS_ZREMRANGEBYSCORE: + + case CMD_REQ_REDIS_RESTORE: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command accepts exactly 3 arguments, otherwise + * return false + */ +static int +redis_arg3(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_LINSERT: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command accepts 0 or more arguments, otherwise + * return false + */ +static int +redis_argn(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_BITCOUNT: + + case CMD_REQ_REDIS_SET: + case CMD_REQ_REDIS_HDEL: + case CMD_REQ_REDIS_HMGET: + case CMD_REQ_REDIS_HMSET: + case CMD_REQ_REDIS_HSCAN: + + case CMD_REQ_REDIS_LPUSH: + case CMD_REQ_REDIS_RPUSH: + + case CMD_REQ_REDIS_SADD: + case CMD_REQ_REDIS_SDIFF: + case CMD_REQ_REDIS_SDIFFSTORE: + case CMD_REQ_REDIS_SINTER: + case CMD_REQ_REDIS_SINTERSTORE: + case CMD_REQ_REDIS_SREM: + case CMD_REQ_REDIS_SUNION: + case CMD_REQ_REDIS_SUNIONSTORE: + case CMD_REQ_REDIS_SRANDMEMBER: + case CMD_REQ_REDIS_SSCAN: + + case CMD_REQ_REDIS_PFADD: + case CMD_REQ_REDIS_PFMERGE: + + case CMD_REQ_REDIS_ZADD: + case CMD_REQ_REDIS_ZINTERSTORE: + case CMD_REQ_REDIS_ZRANGE: + case CMD_REQ_REDIS_ZRANGEBYSCORE: + case CMD_REQ_REDIS_ZREM: + case CMD_REQ_REDIS_ZREVRANGE: + case CMD_REQ_REDIS_ZRANGEBYLEX: + case CMD_REQ_REDIS_ZREVRANGEBYSCORE: + case CMD_REQ_REDIS_ZUNIONSTORE: + case CMD_REQ_REDIS_ZSCAN: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command is a vector command accepting one or + * more keys, otherwise return false + */ +static int +redis_argx(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_MGET: + case CMD_REQ_REDIS_DEL: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command is a vector command accepting one or + * more key-value pairs, otherwise return false + */ +static int +redis_argkvx(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_MSET: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command is either EVAL or EVALSHA. These commands + * have a special format with exactly 2 arguments, followed by one or more keys, + * followed by zero or more arguments (the documentation online seems to suggest + * that at least one argument is required, but that shouldn't be the case). + */ +static int +redis_argeval(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_EVAL: + case CMD_REQ_REDIS_EVALSHA: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Reference: http://redis.io/topics/protocol + * + * Redis >= 1.2 uses the unified protocol to send requests to the Redis + * server. In the unified protocol all the arguments sent to the server + * are binary safe and every request has the following general form: + * + * * CR LF + * $ CR LF + * CR LF + * ... + * $ CR LF + * CR LF + * + * Before the unified request protocol, redis protocol for requests supported + * the following commands + * 1). Inline commands: simple commands where arguments are just space + * separated strings. No binary safeness is possible. + * 2). Bulk commands: bulk commands are exactly like inline commands, but + * the last argument is handled in a special way in order to allow for + * a binary-safe last argument. + * + * only supports the Redis unified protocol for requests. + */ +void +redis_parse_cmd(struct cmd *r) +{ + int len; + char *p, *m, *token = NULL; + char *cmd_end; + char ch; + uint32_t rlen = 0; /* running length in parsing fsa */ + uint32_t rnarg = 0; /* running # arg used by parsing fsa */ + enum { + SW_START, + SW_NARG, + SW_NARG_LF, + SW_REQ_TYPE_LEN, + SW_REQ_TYPE_LEN_LF, + SW_REQ_TYPE, + SW_REQ_TYPE_LF, + SW_KEY_LEN, + SW_KEY_LEN_LF, + SW_KEY, + SW_KEY_LF, + SW_ARG1_LEN, + SW_ARG1_LEN_LF, + SW_ARG1, + SW_ARG1_LF, + SW_ARG2_LEN, + SW_ARG2_LEN_LF, + SW_ARG2, + SW_ARG2_LF, + SW_ARG3_LEN, + SW_ARG3_LEN_LF, + SW_ARG3, + SW_ARG3_LF, + SW_ARGN_LEN, + SW_ARGN_LEN_LF, + SW_ARGN, + SW_ARGN_LF, + SW_SENTINEL + } state; + + state = SW_START; + cmd_end = r->cmd + r->clen; + + ASSERT(state >= SW_START && state < SW_SENTINEL); + ASSERT(r->cmd != NULL && r->clen > 0); + + for (p = r->cmd; p < cmd_end; p++) { + ch = *p; + + switch (state) { + + case SW_START: + case SW_NARG: + if (token == NULL) { + if (ch != '*') { + goto error; + } + token = p; + /* req_start <- p */ + r->narg_start = p; + rnarg = 0; + state = SW_NARG; + } else if (isdigit(ch)) { + rnarg = rnarg * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + if (rnarg == 0) { + goto error; + } + r->narg = rnarg; + r->narg_end = p; + token = NULL; + state = SW_NARG_LF; + } else { + goto error; + } + + break; + + case SW_NARG_LF: + switch (ch) { + case LF: + state = SW_REQ_TYPE_LEN; + break; + + default: + goto error; + } + + break; + + case SW_REQ_TYPE_LEN: + if (token == NULL) { + if (ch != '$') { + goto error; + } + token = p; + rlen = 0; + } else if (isdigit(ch)) { + rlen = rlen * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + if (rlen == 0 || rnarg == 0) { + goto error; + } + rnarg--; + token = NULL; + state = SW_REQ_TYPE_LEN_LF; + } else { + goto error; + } + + break; + + case SW_REQ_TYPE_LEN_LF: + switch (ch) { + case LF: + state = SW_REQ_TYPE; + break; + + default: + goto error; + } + + break; + + case SW_REQ_TYPE: + if (token == NULL) { + token = p; + } + + m = token + rlen; + if (m >= cmd_end) { + //m = cmd_end - 1; + //p = m; + //break; + goto error; + } + + if (*m != CR) { + goto error; + } + + p = m; /* move forward by rlen bytes */ + rlen = 0; + m = token; + token = NULL; + r->type = CMD_UNKNOWN; + + switch (p - m) { + + case 3: + if (str3icmp(m, 'g', 'e', 't')) { + r->type = CMD_REQ_REDIS_GET; + break; + } + + if (str3icmp(m, 's', 'e', 't')) { + r->type = CMD_REQ_REDIS_SET; + break; + } + + if (str3icmp(m, 't', 't', 'l')) { + r->type = CMD_REQ_REDIS_TTL; + break; + } + + if (str3icmp(m, 'd', 'e', 'l')) { + r->type = CMD_REQ_REDIS_DEL; + break; + } + + break; + + case 4: + if (str4icmp(m, 'p', 't', 't', 'l')) { + r->type = CMD_REQ_REDIS_PTTL; + break; + } + + if (str4icmp(m, 'd', 'e', 'c', 'r')) { + r->type = CMD_REQ_REDIS_DECR; + break; + } + + if (str4icmp(m, 'd', 'u', 'm', 'p')) { + r->type = CMD_REQ_REDIS_DUMP; + break; + } + + if (str4icmp(m, 'h', 'd', 'e', 'l')) { + r->type = CMD_REQ_REDIS_HDEL; + break; + } + + if (str4icmp(m, 'h', 'g', 'e', 't')) { + r->type = CMD_REQ_REDIS_HGET; + break; + } + + if (str4icmp(m, 'h', 'l', 'e', 'n')) { + r->type = CMD_REQ_REDIS_HLEN; + break; + } + + if (str4icmp(m, 'h', 's', 'e', 't')) { + r->type = CMD_REQ_REDIS_HSET; + break; + } + + if (str4icmp(m, 'i', 'n', 'c', 'r')) { + r->type = CMD_REQ_REDIS_INCR; + break; + } + + if (str4icmp(m, 'l', 'l', 'e', 'n')) { + r->type = CMD_REQ_REDIS_LLEN; + break; + } + + if (str4icmp(m, 'l', 'p', 'o', 'p')) { + r->type = CMD_REQ_REDIS_LPOP; + break; + } + + if (str4icmp(m, 'l', 'r', 'e', 'm')) { + r->type = CMD_REQ_REDIS_LREM; + break; + } + + if (str4icmp(m, 'l', 's', 'e', 't')) { + r->type = CMD_REQ_REDIS_LSET; + break; + } + + if (str4icmp(m, 'r', 'p', 'o', 'p')) { + r->type = CMD_REQ_REDIS_RPOP; + break; + } + + if (str4icmp(m, 's', 'a', 'd', 'd')) { + r->type = CMD_REQ_REDIS_SADD; + break; + } + + if (str4icmp(m, 's', 'p', 'o', 'p')) { + r->type = CMD_REQ_REDIS_SPOP; + break; + } + + if (str4icmp(m, 's', 'r', 'e', 'm')) { + r->type = CMD_REQ_REDIS_SREM; + break; + } + + if (str4icmp(m, 't', 'y', 'p', 'e')) { + r->type = CMD_REQ_REDIS_TYPE; + break; + } + + if (str4icmp(m, 'm', 'g', 'e', 't')) { + r->type = CMD_REQ_REDIS_MGET; + break; + } + if (str4icmp(m, 'm', 's', 'e', 't')) { + r->type = CMD_REQ_REDIS_MSET; + break; + } + + if (str4icmp(m, 'z', 'a', 'd', 'd')) { + r->type = CMD_REQ_REDIS_ZADD; + break; + } + + if (str4icmp(m, 'z', 'r', 'e', 'm')) { + r->type = CMD_REQ_REDIS_ZREM; + break; + } + + if (str4icmp(m, 'e', 'v', 'a', 'l')) { + r->type = CMD_REQ_REDIS_EVAL; + break; + } + + if (str4icmp(m, 's', 'o', 'r', 't')) { + r->type = CMD_REQ_REDIS_SORT; + break; + } + + if (str4icmp(m, 'p', 'i', 'n', 'g')) { + r->type = CMD_REQ_REDIS_PING; + r->noforward = 1; + break; + } + + if (str4icmp(m, 'q', 'u', 'i', 't')) { + r->type = CMD_REQ_REDIS_QUIT; + r->quit = 1; + break; + } + + if (str4icmp(m, 'a', 'u', 't', 'h')) { + r->type = CMD_REQ_REDIS_AUTH; + r->noforward = 1; + break; + } + + break; + + case 5: + if (str5icmp(m, 'h', 'k', 'e', 'y', 's')) { + r->type = CMD_REQ_REDIS_HKEYS; + break; + } + + if (str5icmp(m, 'h', 'm', 'g', 'e', 't')) { + r->type = CMD_REQ_REDIS_HMGET; + break; + } + + if (str5icmp(m, 'h', 'm', 's', 'e', 't')) { + r->type = CMD_REQ_REDIS_HMSET; + break; + } + + if (str5icmp(m, 'h', 'v', 'a', 'l', 's')) { + r->type = CMD_REQ_REDIS_HVALS; + break; + } + + if (str5icmp(m, 'h', 's', 'c', 'a', 'n')) { + r->type = CMD_REQ_REDIS_HSCAN; + break; + } + + if (str5icmp(m, 'l', 'p', 'u', 's', 'h')) { + r->type = CMD_REQ_REDIS_LPUSH; + break; + } + + if (str5icmp(m, 'l', 't', 'r', 'i', 'm')) { + r->type = CMD_REQ_REDIS_LTRIM; + break; + } + + if (str5icmp(m, 'r', 'p', 'u', 's', 'h')) { + r->type = CMD_REQ_REDIS_RPUSH; + break; + } + + if (str5icmp(m, 's', 'c', 'a', 'r', 'd')) { + r->type = CMD_REQ_REDIS_SCARD; + break; + } + + if (str5icmp(m, 's', 'd', 'i', 'f', 'f')) { + r->type = CMD_REQ_REDIS_SDIFF; + break; + } + + if (str5icmp(m, 's', 'e', 't', 'e', 'x')) { + r->type = CMD_REQ_REDIS_SETEX; + break; + } + + if (str5icmp(m, 's', 'e', 't', 'n', 'x')) { + r->type = CMD_REQ_REDIS_SETNX; + break; + } + + if (str5icmp(m, 's', 'm', 'o', 'v', 'e')) { + r->type = CMD_REQ_REDIS_SMOVE; + break; + } + + if (str5icmp(m, 's', 's', 'c', 'a', 'n')) { + r->type = CMD_REQ_REDIS_SSCAN; + break; + } + + if (str5icmp(m, 'z', 'c', 'a', 'r', 'd')) { + r->type = CMD_REQ_REDIS_ZCARD; + break; + } + + if (str5icmp(m, 'z', 'r', 'a', 'n', 'k')) { + r->type = CMD_REQ_REDIS_ZRANK; + break; + } + + if (str5icmp(m, 'z', 's', 'c', 'a', 'n')) { + r->type = CMD_REQ_REDIS_ZSCAN; + break; + } + + if (str5icmp(m, 'p', 'f', 'a', 'd', 'd')) { + r->type = CMD_REQ_REDIS_PFADD; + break; + } + + break; + + case 6: + if (str6icmp(m, 'a', 'p', 'p', 'e', 'n', 'd')) { + r->type = CMD_REQ_REDIS_APPEND; + break; + } + + if (str6icmp(m, 'd', 'e', 'c', 'r', 'b', 'y')) { + r->type = CMD_REQ_REDIS_DECRBY; + break; + } + + if (str6icmp(m, 'e', 'x', 'i', 's', 't', 's')) { + r->type = CMD_REQ_REDIS_EXISTS; + break; + } + + if (str6icmp(m, 'e', 'x', 'p', 'i', 'r', 'e')) { + r->type = CMD_REQ_REDIS_EXPIRE; + break; + } + + if (str6icmp(m, 'g', 'e', 't', 'b', 'i', 't')) { + r->type = CMD_REQ_REDIS_GETBIT; + break; + } + + if (str6icmp(m, 'g', 'e', 't', 's', 'e', 't')) { + r->type = CMD_REQ_REDIS_GETSET; + break; + } + + if (str6icmp(m, 'p', 's', 'e', 't', 'e', 'x')) { + r->type = CMD_REQ_REDIS_PSETEX; + break; + } + + if (str6icmp(m, 'h', 's', 'e', 't', 'n', 'x')) { + r->type = CMD_REQ_REDIS_HSETNX; + break; + } + + if (str6icmp(m, 'i', 'n', 'c', 'r', 'b', 'y')) { + r->type = CMD_REQ_REDIS_INCRBY; + break; + } + + if (str6icmp(m, 'l', 'i', 'n', 'd', 'e', 'x')) { + r->type = CMD_REQ_REDIS_LINDEX; + break; + } + + if (str6icmp(m, 'l', 'p', 'u', 's', 'h', 'x')) { + r->type = CMD_REQ_REDIS_LPUSHX; + break; + } + + if (str6icmp(m, 'l', 'r', 'a', 'n', 'g', 'e')) { + r->type = CMD_REQ_REDIS_LRANGE; + break; + } + + if (str6icmp(m, 'r', 'p', 'u', 's', 'h', 'x')) { + r->type = CMD_REQ_REDIS_RPUSHX; + break; + } + + if (str6icmp(m, 's', 'e', 't', 'b', 'i', 't')) { + r->type = CMD_REQ_REDIS_SETBIT; + break; + } + + if (str6icmp(m, 's', 'i', 'n', 't', 'e', 'r')) { + r->type = CMD_REQ_REDIS_SINTER; + break; + } + + if (str6icmp(m, 's', 't', 'r', 'l', 'e', 'n')) { + r->type = CMD_REQ_REDIS_STRLEN; + break; + } + + if (str6icmp(m, 's', 'u', 'n', 'i', 'o', 'n')) { + r->type = CMD_REQ_REDIS_SUNION; + break; + } + + if (str6icmp(m, 'z', 'c', 'o', 'u', 'n', 't')) { + r->type = CMD_REQ_REDIS_ZCOUNT; + break; + } + + if (str6icmp(m, 'z', 'r', 'a', 'n', 'g', 'e')) { + r->type = CMD_REQ_REDIS_ZRANGE; + break; + } + + if (str6icmp(m, 'z', 's', 'c', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_ZSCORE; + break; + } + + break; + + case 7: + if (str7icmp(m, 'p', 'e', 'r', 's', 'i', 's', 't')) { + r->type = CMD_REQ_REDIS_PERSIST; + break; + } + + if (str7icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e')) { + r->type = CMD_REQ_REDIS_PEXPIRE; + break; + } + + if (str7icmp(m, 'h', 'e', 'x', 'i', 's', 't', 's')) { + r->type = CMD_REQ_REDIS_HEXISTS; + break; + } + + if (str7icmp(m, 'h', 'g', 'e', 't', 'a', 'l', 'l')) { + r->type = CMD_REQ_REDIS_HGETALL; + break; + } + + if (str7icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y')) { + r->type = CMD_REQ_REDIS_HINCRBY; + break; + } + + if (str7icmp(m, 'l', 'i', 'n', 's', 'e', 'r', 't')) { + r->type = CMD_REQ_REDIS_LINSERT; + break; + } + + if (str7icmp(m, 'z', 'i', 'n', 'c', 'r', 'b', 'y')) { + r->type = CMD_REQ_REDIS_ZINCRBY; + break; + } + + if (str7icmp(m, 'e', 'v', 'a', 'l', 's', 'h', 'a')) { + r->type = CMD_REQ_REDIS_EVALSHA; + break; + } + + if (str7icmp(m, 'r', 'e', 's', 't', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_RESTORE; + break; + } + + if (str7icmp(m, 'p', 'f', 'c', 'o', 'u', 'n', 't')) { + r->type = CMD_REQ_REDIS_PFCOUNT; + break; + } + + if (str7icmp(m, 'p', 'f', 'm', 'e', 'r', 'g', 'e')) { + r->type = CMD_REQ_REDIS_PFMERGE; + break; + } + + break; + + case 8: + if (str8icmp(m, 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) { + r->type = CMD_REQ_REDIS_EXPIREAT; + break; + } + + if (str8icmp(m, 'b', 'i', 't', 'c', 'o', 'u', 'n', 't')) { + r->type = CMD_REQ_REDIS_BITCOUNT; + break; + } + + if (str8icmp(m, 'g', 'e', 't', 'r', 'a', 'n', 'g', 'e')) { + r->type = CMD_REQ_REDIS_GETRANGE; + break; + } + + if (str8icmp(m, 's', 'e', 't', 'r', 'a', 'n', 'g', 'e')) { + r->type = CMD_REQ_REDIS_SETRANGE; + break; + } + + if (str8icmp(m, 's', 'm', 'e', 'm', 'b', 'e', 'r', 's')) { + r->type = CMD_REQ_REDIS_SMEMBERS; + break; + } + + if (str8icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'k')) { + r->type = CMD_REQ_REDIS_ZREVRANK; + break; + } + + break; + + case 9: + if (str9icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) { + r->type = CMD_REQ_REDIS_PEXPIREAT; + break; + } + + if (str9icmp(m, 'r', 'p', 'o', 'p', 'l', 'p', 'u', 's', 'h')) { + r->type = CMD_REQ_REDIS_RPOPLPUSH; + break; + } + + if (str9icmp(m, 's', 'i', 's', 'm', 'e', 'm', 'b', 'e', 'r')) { + r->type = CMD_REQ_REDIS_SISMEMBER; + break; + } + + if (str9icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e')) { + r->type = CMD_REQ_REDIS_ZREVRANGE; + break; + } + + if (str9icmp(m, 'z', 'l', 'e', 'x', 'c', 'o', 'u', 'n', 't')) { + r->type = CMD_REQ_REDIS_ZLEXCOUNT; + break; + } + + break; + + case 10: + if (str10icmp(m, 's', 'd', 'i', 'f', 'f', 's', 't', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_SDIFFSTORE; + break; + } + + case 11: + if (str11icmp(m, 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { + r->type = CMD_REQ_REDIS_INCRBYFLOAT; + break; + } + + if (str11icmp(m, 's', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_SINTERSTORE; + break; + } + + if (str11icmp(m, 's', 'r', 'a', 'n', 'd', 'm', 'e', 'm', 'b', 'e', 'r')) { + r->type = CMD_REQ_REDIS_SRANDMEMBER; + break; + } + + if (str11icmp(m, 's', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_SUNIONSTORE; + break; + } + + if (str11icmp(m, 'z', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_ZINTERSTORE; + break; + } + + if (str11icmp(m, 'z', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_ZUNIONSTORE; + break; + } + + if (str11icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) { + r->type = CMD_REQ_REDIS_ZRANGEBYLEX; + break; + } + + break; + + case 12: + if (str12icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { + r->type = CMD_REQ_REDIS_HINCRBYFLOAT; + break; + } + + + break; + + case 13: + if (str13icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_ZRANGEBYSCORE; + break; + } + + break; + + case 14: + if (str14icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) { + r->type = CMD_REQ_REDIS_ZREMRANGEBYLEX; + break; + } + + break; + + case 15: + if (str15icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'r', 'a', 'n', 'k')) { + r->type = CMD_REQ_REDIS_ZREMRANGEBYRANK; + break; + } + + break; + + case 16: + if (str16icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_ZREMRANGEBYSCORE; + break; + } + + if (str16icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_ZREVRANGEBYSCORE; + break; + } + + break; + + default: + break; + } + + if (r->type == CMD_UNKNOWN) { + goto error; + } + + state = SW_REQ_TYPE_LF; + break; + + case SW_REQ_TYPE_LF: + switch (ch) { + case LF: + if (redis_argz(r)) { + goto done; + } else if (redis_argeval(r)) { + state = SW_ARG1_LEN; + } else { + state = SW_KEY_LEN; + } + break; + + default: + goto error; + } + + break; + + case SW_KEY_LEN: + if (token == NULL) { + if (ch != '$') { + goto error; + } + token = p; + rlen = 0; + } else if (isdigit(ch)) { + rlen = rlen * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + + if (rnarg == 0) { + goto error; + } + rnarg--; + token = NULL; + state = SW_KEY_LEN_LF; + } else { + goto error; + } + + break; + + case SW_KEY_LEN_LF: + switch (ch) { + case LF: + state = SW_KEY; + break; + + default: + goto error; + } + + break; + + case SW_KEY: + if (token == NULL) { + token = p; + } + + m = token + rlen; + if (m >= cmd_end) { + //m = b->last - 1; + //p = m; + //break; + goto error; + } + + if (*m != CR) { + goto error; + } else { /* got a key */ + struct keypos *kpos; + + p = m; /* move forward by rlen bytes */ + rlen = 0; + m = token; + token = NULL; + + kpos = hiarray_push(r->keys); + if (kpos == NULL) { + goto enomem; + } + kpos->start = m; + kpos->end = p; + //kpos->v_len = 0; + + state = SW_KEY_LF; + } + + break; + + case SW_KEY_LF: + switch (ch) { + case LF: + if (redis_arg0(r)) { + if (rnarg != 0) { + goto error; + } + goto done; + } else if (redis_arg1(r)) { + if (rnarg != 1) { + goto error; + } + state = SW_ARG1_LEN; + } else if (redis_arg2(r)) { + if (rnarg != 2) { + goto error; + } + state = SW_ARG1_LEN; + } else if (redis_arg3(r)) { + if (rnarg != 3) { + goto error; + } + state = SW_ARG1_LEN; + } else if (redis_argn(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_ARG1_LEN; + } else if (redis_argx(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_KEY_LEN; + } else if (redis_argkvx(r)) { + if (rnarg == 0) { + goto done; + } + if (r->narg % 2 == 0) { + goto error; + } + state = SW_ARG1_LEN; + } else if (redis_argeval(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_ARGN_LEN; + } else { + goto error; + } + + break; + + default: + goto error; + } + + break; + + case SW_ARG1_LEN: + if (token == NULL) { + if (ch != '$') { + goto error; + } + rlen = 0; + token = p; + } else if (isdigit(ch)) { + rlen = rlen * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + if ((p - token) <= 1 || rnarg == 0) { + goto error; + } + rnarg--; + token = NULL; + + /* + //for mset value length + if(redis_argkvx(r)) + { + struct keypos *kpos; + uint32_t array_len = array_n(r->keys); + if(array_len == 0) + { + goto error; + } + + kpos = array_n(r->keys, array_len-1); + if (kpos == NULL || kpos->v_len != 0) { + goto error; + } + + kpos->v_len = rlen; + } + */ + state = SW_ARG1_LEN_LF; + } else { + goto error; + } + + break; + + case SW_ARG1_LEN_LF: + switch (ch) { + case LF: + state = SW_ARG1; + break; + + default: + goto error; + } + + break; + + case SW_ARG1: + m = p + rlen; + if (m >= cmd_end) { + //rlen -= (uint32_t)(b->last - p); + //m = b->last - 1; + //p = m; + //break; + goto error; + } + + if (*m != CR) { + goto error; + } + + p = m; /* move forward by rlen bytes */ + rlen = 0; + + state = SW_ARG1_LF; + + break; + + case SW_ARG1_LF: + switch (ch) { + case LF: + if (redis_arg1(r)) { + if (rnarg != 0) { + goto error; + } + goto done; + } else if (redis_arg2(r)) { + if (rnarg != 1) { + goto error; + } + state = SW_ARG2_LEN; + } else if (redis_arg3(r)) { + if (rnarg != 2) { + goto error; + } + state = SW_ARG2_LEN; + } else if (redis_argn(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_ARGN_LEN; + } else if (redis_argeval(r)) { + if (rnarg < 2) { + goto error; + } + state = SW_ARG2_LEN; + } else if (redis_argkvx(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_KEY_LEN; + } else { + goto error; + } + + break; + + default: + goto error; + } + + break; + + case SW_ARG2_LEN: + if (token == NULL) { + if (ch != '$') { + goto error; + } + rlen = 0; + token = p; + } else if (isdigit(ch)) { + rlen = rlen * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + if ((p - token) <= 1 || rnarg == 0) { + goto error; + } + rnarg--; + token = NULL; + state = SW_ARG2_LEN_LF; + } else { + goto error; + } + + break; + + case SW_ARG2_LEN_LF: + switch (ch) { + case LF: + state = SW_ARG2; + break; + + default: + goto error; + } + + break; + + case SW_ARG2: + if (token == NULL && redis_argeval(r)) { + /* + * For EVAL/EVALSHA, ARG2 represents the # key/arg pairs which must + * be tokenized and stored in contiguous memory. + */ + token = p; + } + + m = p + rlen; + if (m >= cmd_end) { + //rlen -= (uint32_t)(b->last - p); + //m = b->last - 1; + //p = m; + //break; + goto error; + } + + if (*m != CR) { + goto error; + } + + p = m; /* move forward by rlen bytes */ + rlen = 0; + + if (redis_argeval(r)) { + uint32_t nkey; + char *chp; + + /* + * For EVAL/EVALSHA, we need to find the integer value of this + * argument. It tells us the number of keys in the script, and + * we need to error out if number of keys is 0. At this point, + * both p and m point to the end of the argument and r->token + * points to the start. + */ + if (p - token < 1) { + goto error; + } + + for (nkey = 0, chp = token; chp < p; chp++) { + if (isdigit(*chp)) { + nkey = nkey * 10 + (uint32_t)(*chp - '0'); + } else { + goto error; + } + } + if (nkey == 0) { + goto error; + } + + token = NULL; + } + + state = SW_ARG2_LF; + + break; + + case SW_ARG2_LF: + switch (ch) { + case LF: + if (redis_arg2(r)) { + if (rnarg != 0) { + goto error; + } + goto done; + } else if (redis_arg3(r)) { + if (rnarg != 1) { + goto error; + } + state = SW_ARG3_LEN; + } else if (redis_argn(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_ARGN_LEN; + } else if (redis_argeval(r)) { + if (rnarg < 1) { + goto error; + } + state = SW_KEY_LEN; + } else { + goto error; + } + + break; + + default: + goto error; + } + + break; + + case SW_ARG3_LEN: + if (token == NULL) { + if (ch != '$') { + goto error; + } + rlen = 0; + token = p; + } else if (isdigit(ch)) { + rlen = rlen * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + if ((p - token) <= 1 || rnarg == 0) { + goto error; + } + rnarg--; + token = NULL; + state = SW_ARG3_LEN_LF; + } else { + goto error; + } + + break; + + case SW_ARG3_LEN_LF: + switch (ch) { + case LF: + state = SW_ARG3; + break; + + default: + goto error; + } + + break; + + case SW_ARG3: + m = p + rlen; + if (m >= cmd_end) { + //rlen -= (uint32_t)(b->last - p); + //m = b->last - 1; + //p = m; + //break; + goto error; + } + + if (*m != CR) { + goto error; + } + + p = m; /* move forward by rlen bytes */ + rlen = 0; + state = SW_ARG3_LF; + + break; + + case SW_ARG3_LF: + switch (ch) { + case LF: + if (redis_arg3(r)) { + if (rnarg != 0) { + goto error; + } + goto done; + } else if (redis_argn(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_ARGN_LEN; + } else { + goto error; + } + + break; + + default: + goto error; + } + + break; + + case SW_ARGN_LEN: + if (token == NULL) { + if (ch != '$') { + goto error; + } + rlen = 0; + token = p; + } else if (isdigit(ch)) { + rlen = rlen * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + if ((p - token) <= 1 || rnarg == 0) { + goto error; + } + rnarg--; + token = NULL; + state = SW_ARGN_LEN_LF; + } else { + goto error; + } + + break; + + case SW_ARGN_LEN_LF: + switch (ch) { + case LF: + state = SW_ARGN; + break; + + default: + goto error; + } + + break; + + case SW_ARGN: + m = p + rlen; + if (m >= cmd_end) { + //rlen -= (uint32_t)(b->last - p); + //m = b->last - 1; + //p = m; + //break; + goto error; + } + + if (*m != CR) { + goto error; + } + + p = m; /* move forward by rlen bytes */ + rlen = 0; + state = SW_ARGN_LF; + + break; + + case SW_ARGN_LF: + switch (ch) { + case LF: + if (redis_argn(r) || redis_argeval(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_ARGN_LEN; + } else { + goto error; + } + + break; + + default: + goto error; + } + + break; + + case SW_SENTINEL: + default: + NOT_REACHED(); + break; + } + } + + ASSERT(p == cmd_end); + + return; + +done: + + ASSERT(r->type > CMD_UNKNOWN && r->type < CMD_SENTINEL); + + r->result = CMD_PARSE_OK; + + return; + +enomem: + + r->result = CMD_PARSE_ENOMEM; + + return; + +error: + + r->result = CMD_PARSE_ERROR; + errno = EINVAL; + if(r->errstr == NULL){ + r->errstr = hi_alloc(100*sizeof(*r->errstr)); + } + + len = _scnprintf(r->errstr, 100, "Parse command error. Cmd type: %d, state: %d, break position: %d.", + r->type, state, (int)(p - r->cmd)); + r->errstr[len] = '\0'; +} + +struct cmd *command_get() +{ + struct cmd *command; + command = hi_alloc(sizeof(struct cmd)); + if(command == NULL) + { + return NULL; + } + + command->id = ++cmd_id; + command->result = CMD_PARSE_OK; + command->errstr = NULL; + command->type = CMD_UNKNOWN; + command->cmd = NULL; + command->clen = 0; + command->keys = NULL; + command->narg_start = NULL; + command->narg_end = NULL; + command->narg = 0; + command->quit = 0; + command->noforward = 0; + command->slot_num = -1; + command->frag_seq = NULL; + command->reply = NULL; + command->sub_commands = NULL; + + command->keys = hiarray_create(1, sizeof(struct keypos)); + if (command->keys == NULL) + { + hi_free(command); + return NULL; + } + + return command; +} + +void command_destroy(struct cmd *command) +{ + if(command == NULL) + { + return; + } + + if(command->cmd != NULL) + { + free(command->cmd); + } + + if(command->errstr != NULL){ + hi_free(command->errstr); + } + + if(command->keys != NULL) + { + command->keys->nelem = 0; + hiarray_destroy(command->keys); + } + + if(command->frag_seq != NULL) + { + hi_free(command->frag_seq); + command->frag_seq = NULL; + } + + if(command->reply != NULL) + { + freeReplyObject(command->reply); + } + + if(command->sub_commands != NULL) + { + listRelease(command->sub_commands); + } + + hi_free(command); +} + + diff --git a/ext/hiredis-vip-0.3.0/command.h b/ext/hiredis-vip-0.3.0/command.h new file mode 100644 index 000000000..b7c388a69 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/command.h @@ -0,0 +1,179 @@ +#ifndef __COMMAND_H_ +#define __COMMAND_H_ + +#include + +#include "hiredis.h" +#include "adlist.h" + +typedef enum cmd_parse_result { + CMD_PARSE_OK, /* parsing ok */ + CMD_PARSE_ENOMEM, /* out of memory */ + CMD_PARSE_ERROR, /* parsing error */ + CMD_PARSE_REPAIR, /* more to parse -> repair parsed & unparsed data */ + CMD_PARSE_AGAIN, /* incomplete -> parse again */ +} cmd_parse_result_t; + +#define CMD_TYPE_CODEC(ACTION) \ + ACTION( UNKNOWN ) \ + ACTION( REQ_REDIS_DEL ) /* redis commands - keys */ \ + ACTION( REQ_REDIS_EXISTS ) \ + ACTION( REQ_REDIS_EXPIRE ) \ + ACTION( REQ_REDIS_EXPIREAT ) \ + ACTION( REQ_REDIS_PEXPIRE ) \ + ACTION( REQ_REDIS_PEXPIREAT ) \ + ACTION( REQ_REDIS_PERSIST ) \ + ACTION( REQ_REDIS_PTTL ) \ + ACTION( REQ_REDIS_SORT ) \ + ACTION( REQ_REDIS_TTL ) \ + ACTION( REQ_REDIS_TYPE ) \ + ACTION( REQ_REDIS_APPEND ) /* redis requests - string */ \ + ACTION( REQ_REDIS_BITCOUNT ) \ + ACTION( REQ_REDIS_DECR ) \ + ACTION( REQ_REDIS_DECRBY ) \ + ACTION( REQ_REDIS_DUMP ) \ + ACTION( REQ_REDIS_GET ) \ + ACTION( REQ_REDIS_GETBIT ) \ + ACTION( REQ_REDIS_GETRANGE ) \ + ACTION( REQ_REDIS_GETSET ) \ + ACTION( REQ_REDIS_INCR ) \ + ACTION( REQ_REDIS_INCRBY ) \ + ACTION( REQ_REDIS_INCRBYFLOAT ) \ + ACTION( REQ_REDIS_MGET ) \ + ACTION( REQ_REDIS_MSET ) \ + ACTION( REQ_REDIS_PSETEX ) \ + ACTION( REQ_REDIS_RESTORE ) \ + ACTION( REQ_REDIS_SET ) \ + ACTION( REQ_REDIS_SETBIT ) \ + ACTION( REQ_REDIS_SETEX ) \ + ACTION( REQ_REDIS_SETNX ) \ + ACTION( REQ_REDIS_SETRANGE ) \ + ACTION( REQ_REDIS_STRLEN ) \ + ACTION( REQ_REDIS_HDEL ) /* redis requests - hashes */ \ + ACTION( REQ_REDIS_HEXISTS ) \ + ACTION( REQ_REDIS_HGET ) \ + ACTION( REQ_REDIS_HGETALL ) \ + ACTION( REQ_REDIS_HINCRBY ) \ + ACTION( REQ_REDIS_HINCRBYFLOAT ) \ + ACTION( REQ_REDIS_HKEYS ) \ + ACTION( REQ_REDIS_HLEN ) \ + ACTION( REQ_REDIS_HMGET ) \ + ACTION( REQ_REDIS_HMSET ) \ + ACTION( REQ_REDIS_HSET ) \ + ACTION( REQ_REDIS_HSETNX ) \ + ACTION( REQ_REDIS_HSCAN) \ + ACTION( REQ_REDIS_HVALS ) \ + ACTION( REQ_REDIS_LINDEX ) /* redis requests - lists */ \ + ACTION( REQ_REDIS_LINSERT ) \ + ACTION( REQ_REDIS_LLEN ) \ + ACTION( REQ_REDIS_LPOP ) \ + ACTION( REQ_REDIS_LPUSH ) \ + ACTION( REQ_REDIS_LPUSHX ) \ + ACTION( REQ_REDIS_LRANGE ) \ + ACTION( REQ_REDIS_LREM ) \ + ACTION( REQ_REDIS_LSET ) \ + ACTION( REQ_REDIS_LTRIM ) \ + ACTION( REQ_REDIS_PFADD ) /* redis requests - hyperloglog */ \ + ACTION( REQ_REDIS_PFCOUNT ) \ + ACTION( REQ_REDIS_PFMERGE ) \ + ACTION( REQ_REDIS_RPOP ) \ + ACTION( REQ_REDIS_RPOPLPUSH ) \ + ACTION( REQ_REDIS_RPUSH ) \ + ACTION( REQ_REDIS_RPUSHX ) \ + ACTION( REQ_REDIS_SADD ) /* redis requests - sets */ \ + ACTION( REQ_REDIS_SCARD ) \ + ACTION( REQ_REDIS_SDIFF ) \ + ACTION( REQ_REDIS_SDIFFSTORE ) \ + ACTION( REQ_REDIS_SINTER ) \ + ACTION( REQ_REDIS_SINTERSTORE ) \ + ACTION( REQ_REDIS_SISMEMBER ) \ + ACTION( REQ_REDIS_SMEMBERS ) \ + ACTION( REQ_REDIS_SMOVE ) \ + ACTION( REQ_REDIS_SPOP ) \ + ACTION( REQ_REDIS_SRANDMEMBER ) \ + ACTION( REQ_REDIS_SREM ) \ + ACTION( REQ_REDIS_SUNION ) \ + ACTION( REQ_REDIS_SUNIONSTORE ) \ + ACTION( REQ_REDIS_SSCAN) \ + ACTION( REQ_REDIS_ZADD ) /* redis requests - sorted sets */ \ + ACTION( REQ_REDIS_ZCARD ) \ + ACTION( REQ_REDIS_ZCOUNT ) \ + ACTION( REQ_REDIS_ZINCRBY ) \ + ACTION( REQ_REDIS_ZINTERSTORE ) \ + ACTION( REQ_REDIS_ZLEXCOUNT ) \ + ACTION( REQ_REDIS_ZRANGE ) \ + ACTION( REQ_REDIS_ZRANGEBYLEX ) \ + ACTION( REQ_REDIS_ZRANGEBYSCORE ) \ + ACTION( REQ_REDIS_ZRANK ) \ + ACTION( REQ_REDIS_ZREM ) \ + ACTION( REQ_REDIS_ZREMRANGEBYRANK ) \ + ACTION( REQ_REDIS_ZREMRANGEBYLEX ) \ + ACTION( REQ_REDIS_ZREMRANGEBYSCORE ) \ + ACTION( REQ_REDIS_ZREVRANGE ) \ + ACTION( REQ_REDIS_ZREVRANGEBYSCORE ) \ + ACTION( REQ_REDIS_ZREVRANK ) \ + ACTION( REQ_REDIS_ZSCORE ) \ + ACTION( REQ_REDIS_ZUNIONSTORE ) \ + ACTION( REQ_REDIS_ZSCAN) \ + ACTION( REQ_REDIS_EVAL ) /* redis requests - eval */ \ + ACTION( REQ_REDIS_EVALSHA ) \ + ACTION( REQ_REDIS_PING ) /* redis requests - ping/quit */ \ + ACTION( REQ_REDIS_QUIT) \ + ACTION( REQ_REDIS_AUTH) \ + ACTION( RSP_REDIS_STATUS ) /* redis response */ \ + ACTION( RSP_REDIS_ERROR ) \ + ACTION( RSP_REDIS_INTEGER ) \ + ACTION( RSP_REDIS_BULK ) \ + ACTION( RSP_REDIS_MULTIBULK ) \ + ACTION( SENTINEL ) \ + + +#define DEFINE_ACTION(_name) CMD_##_name, +typedef enum cmd_type { + CMD_TYPE_CODEC(DEFINE_ACTION) +} cmd_type_t; +#undef DEFINE_ACTION + + +struct keypos { + char *start; /* key start pos */ + char *end; /* key end pos */ + uint32_t remain_len; /* remain length after keypos->end for more key-value pairs in command, like mset */ +}; + +struct cmd { + + uint64_t id; /* command id */ + + cmd_parse_result_t result; /* command parsing result */ + char *errstr; /* error info when the command parse failed */ + + cmd_type_t type; /* command type */ + + char *cmd; + uint32_t clen; /* command length */ + + struct hiarray *keys; /* array of keypos, for req */ + + char *narg_start; /* narg start (redis) */ + char *narg_end; /* narg end (redis) */ + uint32_t narg; /* # arguments (redis) */ + + unsigned quit:1; /* quit request? */ + unsigned noforward:1; /* not need forward (example: ping) */ + + int slot_num; /* this command should send to witch slot? + * -1:the keys in this command cross different slots*/ + struct cmd **frag_seq; /* sequence of fragment command, map from keys to fragments*/ + + redisReply *reply; + + hilist *sub_commands; /* just for pipeline and multi-key commands */ +}; + +void redis_parse_cmd(struct cmd *r); + +struct cmd *command_get(void); +void command_destroy(struct cmd *command); + +#endif diff --git a/ext/hiredis-vip-0.3.0/crc16.c b/ext/hiredis-vip-0.3.0/crc16.c new file mode 100644 index 000000000..0f304f6e4 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/crc16.c @@ -0,0 +1,87 @@ +/* + * Copyright 2001-2010 Georges Menie (www.menie.org) + * Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style) + * 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 the University of California, Berkeley 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 REGENTS 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 REGENTS AND 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. + */ + +/* CRC16 implementation according to CCITT standards. + * + * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the + * following parameters: + * + * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" + * Width : 16 bit + * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) + * Initialization : 0000 + * Reflect Input byte : False + * Reflect Output CRC : False + * Xor constant to output CRC : 0000 + * Output for "123456789" : 31C3 + */ +#include "hiutil.h" + +static const uint16_t crc16tab[256]= { + 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, + 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, + 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, + 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, + 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, + 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, + 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, + 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, + 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, + 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, + 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, + 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, + 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, + 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, + 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, + 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, + 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, + 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, + 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, + 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, + 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, + 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, + 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, + 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, + 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, + 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, + 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, + 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, + 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, + 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, + 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, + 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 +}; + +uint16_t crc16(const char *buf, int len) { + int counter; + uint16_t crc = 0; + for (counter = 0; counter < len; counter++) + crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF]; + return crc; +} diff --git a/ext/hiredis-vip-0.3.0/dict.c b/ext/hiredis-vip-0.3.0/dict.c new file mode 100644 index 000000000..79b1041ca --- /dev/null +++ b/ext/hiredis-vip-0.3.0/dict.c @@ -0,0 +1,338 @@ +/* Hash table implementation. + * + * This file implements in memory hash tables with insert/del/replace/find/ + * get-random-element operations. Hash tables will auto resize if needed + * tables of power of two in size are used, collisions are handled by + * chaining. See the source code for more information... :) + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * 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 "fmacros.h" +#include +#include +#include +#include "dict.h" + +/* -------------------------- private prototypes ---------------------------- */ + +static int _dictExpandIfNeeded(dict *ht); +static unsigned long _dictNextPower(unsigned long size); +static int _dictKeyIndex(dict *ht, const void *key); +static int _dictInit(dict *ht, dictType *type, void *privDataPtr); + +/* -------------------------- hash functions -------------------------------- */ + +/* Generic hash function (a popular one from Bernstein). + * I tested a few and this was the best. */ +static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { + unsigned int hash = 5381; + + while (len--) + hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ + return hash; +} + +/* ----------------------------- API implementation ------------------------- */ + +/* Reset an hashtable already initialized with ht_init(). + * NOTE: This function should only called by ht_destroy(). */ +static void _dictReset(dict *ht) { + ht->table = NULL; + ht->size = 0; + ht->sizemask = 0; + ht->used = 0; +} + +/* Create a new hash table */ +static dict *dictCreate(dictType *type, void *privDataPtr) { + dict *ht = malloc(sizeof(*ht)); + _dictInit(ht,type,privDataPtr); + return ht; +} + +/* Initialize the hash table */ +static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { + _dictReset(ht); + ht->type = type; + ht->privdata = privDataPtr; + return DICT_OK; +} + +/* Expand or create the hashtable */ +static int dictExpand(dict *ht, unsigned long size) { + dict n; /* the new hashtable */ + unsigned long realsize = _dictNextPower(size), i; + + /* the size is invalid if it is smaller than the number of + * elements already inside the hashtable */ + if (ht->used > size) + return DICT_ERR; + + _dictInit(&n, ht->type, ht->privdata); + n.size = realsize; + n.sizemask = realsize-1; + n.table = calloc(realsize,sizeof(dictEntry*)); + + /* Copy all the elements from the old to the new table: + * note that if the old hash table is empty ht->size is zero, + * so dictExpand just creates an hash table. */ + n.used = ht->used; + for (i = 0; i < ht->size && ht->used > 0; i++) { + dictEntry *he, *nextHe; + + if (ht->table[i] == NULL) continue; + + /* For each hash entry on this slot... */ + he = ht->table[i]; + while(he) { + unsigned int h; + + nextHe = he->next; + /* Get the new element index */ + h = dictHashKey(ht, he->key) & n.sizemask; + he->next = n.table[h]; + n.table[h] = he; + ht->used--; + /* Pass to the next element */ + he = nextHe; + } + } + assert(ht->used == 0); + free(ht->table); + + /* Remap the new hashtable in the old */ + *ht = n; + return DICT_OK; +} + +/* Add an element to the target hash table */ +static int dictAdd(dict *ht, void *key, void *val) { + int index; + dictEntry *entry; + + /* Get the index of the new element, or -1 if + * the element already exists. */ + if ((index = _dictKeyIndex(ht, key)) == -1) + return DICT_ERR; + + /* Allocates the memory and stores key */ + entry = malloc(sizeof(*entry)); + entry->next = ht->table[index]; + ht->table[index] = entry; + + /* Set the hash entry fields. */ + dictSetHashKey(ht, entry, key); + dictSetHashVal(ht, entry, val); + ht->used++; + return DICT_OK; +} + +/* Add an element, discarding the old if the key already exists. + * Return 1 if the key was added from scratch, 0 if there was already an + * element with such key and dictReplace() just performed a value update + * operation. */ +static int dictReplace(dict *ht, void *key, void *val) { + dictEntry *entry, auxentry; + + /* Try to add the element. If the key + * does not exists dictAdd will suceed. */ + if (dictAdd(ht, key, val) == DICT_OK) + return 1; + /* It already exists, get the entry */ + entry = dictFind(ht, key); + /* Free the old value and set the new one */ + /* Set the new value and free the old one. Note that it is important + * to do that in this order, as the value may just be exactly the same + * as the previous one. In this context, think to reference counting, + * you want to increment (set), and then decrement (free), and not the + * reverse. */ + auxentry = *entry; + dictSetHashVal(ht, entry, val); + dictFreeEntryVal(ht, &auxentry); + return 0; +} + +/* Search and remove an element */ +static int dictDelete(dict *ht, const void *key) { + unsigned int h; + dictEntry *de, *prevde; + + if (ht->size == 0) + return DICT_ERR; + h = dictHashKey(ht, key) & ht->sizemask; + de = ht->table[h]; + + prevde = NULL; + while(de) { + if (dictCompareHashKeys(ht,key,de->key)) { + /* Unlink the element from the list */ + if (prevde) + prevde->next = de->next; + else + ht->table[h] = de->next; + + dictFreeEntryKey(ht,de); + dictFreeEntryVal(ht,de); + free(de); + ht->used--; + return DICT_OK; + } + prevde = de; + de = de->next; + } + return DICT_ERR; /* not found */ +} + +/* Destroy an entire hash table */ +static int _dictClear(dict *ht) { + unsigned long i; + + /* Free all the elements */ + for (i = 0; i < ht->size && ht->used > 0; i++) { + dictEntry *he, *nextHe; + + if ((he = ht->table[i]) == NULL) continue; + while(he) { + nextHe = he->next; + dictFreeEntryKey(ht, he); + dictFreeEntryVal(ht, he); + free(he); + ht->used--; + he = nextHe; + } + } + /* Free the table and the allocated cache structure */ + free(ht->table); + /* Re-initialize the table */ + _dictReset(ht); + return DICT_OK; /* never fails */ +} + +/* Clear & Release the hash table */ +static void dictRelease(dict *ht) { + _dictClear(ht); + free(ht); +} + +static dictEntry *dictFind(dict *ht, const void *key) { + dictEntry *he; + unsigned int h; + + if (ht->size == 0) return NULL; + h = dictHashKey(ht, key) & ht->sizemask; + he = ht->table[h]; + while(he) { + if (dictCompareHashKeys(ht, key, he->key)) + return he; + he = he->next; + } + return NULL; +} + +static dictIterator *dictGetIterator(dict *ht) { + dictIterator *iter = malloc(sizeof(*iter)); + + iter->ht = ht; + iter->index = -1; + iter->entry = NULL; + iter->nextEntry = NULL; + return iter; +} + +static dictEntry *dictNext(dictIterator *iter) { + while (1) { + if (iter->entry == NULL) { + iter->index++; + if (iter->index >= + (signed)iter->ht->size) break; + iter->entry = iter->ht->table[iter->index]; + } else { + iter->entry = iter->nextEntry; + } + if (iter->entry) { + /* We need to save the 'next' here, the iterator user + * may delete the entry we are returning. */ + iter->nextEntry = iter->entry->next; + return iter->entry; + } + } + return NULL; +} + +static void dictReleaseIterator(dictIterator *iter) { + free(iter); +} + +/* ------------------------- private functions ------------------------------ */ + +/* Expand the hash table if needed */ +static int _dictExpandIfNeeded(dict *ht) { + /* If the hash table is empty expand it to the intial size, + * if the table is "full" dobule its size. */ + if (ht->size == 0) + return dictExpand(ht, DICT_HT_INITIAL_SIZE); + if (ht->used == ht->size) + return dictExpand(ht, ht->size*2); + return DICT_OK; +} + +/* Our hash table capability is a power of two */ +static unsigned long _dictNextPower(unsigned long size) { + unsigned long i = DICT_HT_INITIAL_SIZE; + + if (size >= LONG_MAX) return LONG_MAX; + while(1) { + if (i >= size) + return i; + i *= 2; + } +} + +/* Returns the index of a free slot that can be populated with + * an hash entry for the given 'key'. + * If the key already exists, -1 is returned. */ +static int _dictKeyIndex(dict *ht, const void *key) { + unsigned int h; + dictEntry *he; + + /* Expand the hashtable if needed */ + if (_dictExpandIfNeeded(ht) == DICT_ERR) + return -1; + /* Compute the key hash value */ + h = dictHashKey(ht, key) & ht->sizemask; + /* Search if this slot does not already contain the given key */ + he = ht->table[h]; + while(he) { + if (dictCompareHashKeys(ht, key, he->key)) + return -1; + he = he->next; + } + return h; +} + diff --git a/ext/hiredis-vip-0.3.0/dict.h b/ext/hiredis-vip-0.3.0/dict.h new file mode 100644 index 000000000..95fcd280e --- /dev/null +++ b/ext/hiredis-vip-0.3.0/dict.h @@ -0,0 +1,126 @@ +/* Hash table implementation. + * + * This file implements in memory hash tables with insert/del/replace/find/ + * get-random-element operations. Hash tables will auto resize if needed + * tables of power of two in size are used, collisions are handled by + * chaining. See the source code for more information... :) + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * 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. + */ + +#ifndef __DICT_H +#define __DICT_H + +#define DICT_OK 0 +#define DICT_ERR 1 + +/* Unused arguments generate annoying warnings... */ +#define DICT_NOTUSED(V) ((void) V) + +typedef struct dictEntry { + void *key; + void *val; + struct dictEntry *next; +} dictEntry; + +typedef struct dictType { + unsigned int (*hashFunction)(const void *key); + void *(*keyDup)(void *privdata, const void *key); + void *(*valDup)(void *privdata, const void *obj); + int (*keyCompare)(void *privdata, const void *key1, const void *key2); + void (*keyDestructor)(void *privdata, void *key); + void (*valDestructor)(void *privdata, void *obj); +} dictType; + +typedef struct dict { + dictEntry **table; + dictType *type; + unsigned long size; + unsigned long sizemask; + unsigned long used; + void *privdata; +} dict; + +typedef struct dictIterator { + dict *ht; + int index; + dictEntry *entry, *nextEntry; +} dictIterator; + +/* This is the initial size of every hash table */ +#define DICT_HT_INITIAL_SIZE 4 + +/* ------------------------------- Macros ------------------------------------*/ +#define dictFreeEntryVal(ht, entry) \ + if ((ht)->type->valDestructor) \ + (ht)->type->valDestructor((ht)->privdata, (entry)->val) + +#define dictSetHashVal(ht, entry, _val_) do { \ + if ((ht)->type->valDup) \ + entry->val = (ht)->type->valDup((ht)->privdata, _val_); \ + else \ + entry->val = (_val_); \ +} while(0) + +#define dictFreeEntryKey(ht, entry) \ + if ((ht)->type->keyDestructor) \ + (ht)->type->keyDestructor((ht)->privdata, (entry)->key) + +#define dictSetHashKey(ht, entry, _key_) do { \ + if ((ht)->type->keyDup) \ + entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ + else \ + entry->key = (_key_); \ +} while(0) + +#define dictCompareHashKeys(ht, key1, key2) \ + (((ht)->type->keyCompare) ? \ + (ht)->type->keyCompare((ht)->privdata, key1, key2) : \ + (key1) == (key2)) + +#define dictHashKey(ht, key) (ht)->type->hashFunction(key) + +#define dictGetEntryKey(he) ((he)->key) +#define dictGetEntryVal(he) ((he)->val) +#define dictSlots(ht) ((ht)->size) +#define dictSize(ht) ((ht)->used) + +/* API */ +static unsigned int dictGenHashFunction(const unsigned char *buf, int len); +static dict *dictCreate(dictType *type, void *privDataPtr); +static int dictExpand(dict *ht, unsigned long size); +static int dictAdd(dict *ht, void *key, void *val); +static int dictReplace(dict *ht, void *key, void *val); +static int dictDelete(dict *ht, const void *key); +static void dictRelease(dict *ht); +static dictEntry * dictFind(dict *ht, const void *key); +static dictIterator *dictGetIterator(dict *ht); +static dictEntry *dictNext(dictIterator *iter); +static void dictReleaseIterator(dictIterator *iter); + +#endif /* __DICT_H */ diff --git a/ext/hiredis-vip-0.3.0/examples/example-ae.c b/ext/hiredis-vip-0.3.0/examples/example-ae.c new file mode 100644 index 000000000..8efa7306a --- /dev/null +++ b/ext/hiredis-vip-0.3.0/examples/example-ae.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +#include +#include +#include + +/* Put event loop in the global scope, so it can be explicitly stopped */ +static aeEventLoop *loop; + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + aeStop(loop); + return; + } + + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + aeStop(loop); + return; + } + + printf("Disconnected...\n"); + aeStop(loop); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + loop = aeCreateEventLoop(64); + redisAeAttach(loop, c); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + aeMain(loop); + return 0; +} + diff --git a/ext/hiredis-vip-0.3.0/examples/example-glib.c b/ext/hiredis-vip-0.3.0/examples/example-glib.c new file mode 100644 index 000000000..d6e10f8e8 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/examples/example-glib.c @@ -0,0 +1,73 @@ +#include + +#include +#include +#include + +static GMainLoop *mainloop; + +static void +connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, + int status) +{ + if (status != REDIS_OK) { + g_printerr("Failed to connect: %s\n", ac->errstr); + g_main_loop_quit(mainloop); + } else { + g_printerr("Connected...\n"); + } +} + +static void +disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, + int status) +{ + if (status != REDIS_OK) { + g_error("Failed to disconnect: %s", ac->errstr); + } else { + g_printerr("Disconnected...\n"); + g_main_loop_quit(mainloop); + } +} + +static void +command_cb(redisAsyncContext *ac, + gpointer r, + gpointer user_data G_GNUC_UNUSED) +{ + redisReply *reply = r; + + if (reply) { + g_print("REPLY: %s\n", reply->str); + } + + redisAsyncDisconnect(ac); +} + +gint +main (gint argc G_GNUC_UNUSED, + gchar *argv[] G_GNUC_UNUSED) +{ + redisAsyncContext *ac; + GMainContext *context = NULL; + GSource *source; + + ac = redisAsyncConnect("127.0.0.1", 6379); + if (ac->err) { + g_printerr("%s\n", ac->errstr); + exit(EXIT_FAILURE); + } + + source = redis_source_new(ac); + mainloop = g_main_loop_new(context, FALSE); + g_source_attach(source, context); + + redisAsyncSetConnectCallback(ac, connect_cb); + redisAsyncSetDisconnectCallback(ac, disconnect_cb); + redisAsyncCommand(ac, command_cb, NULL, "SET key 1234"); + redisAsyncCommand(ac, command_cb, NULL, "GET key"); + + g_main_loop_run(mainloop); + + return EXIT_SUCCESS; +} diff --git a/ext/hiredis-vip-0.3.0/examples/example-libev.c b/ext/hiredis-vip-0.3.0/examples/example-libev.c new file mode 100644 index 000000000..cc8b166ec --- /dev/null +++ b/ext/hiredis-vip-0.3.0/examples/example-libev.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibevAttach(EV_DEFAULT_ c); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + ev_loop(EV_DEFAULT_ 0); + return 0; +} diff --git a/ext/hiredis-vip-0.3.0/examples/example-libevent.c b/ext/hiredis-vip-0.3.0/examples/example-libevent.c new file mode 100644 index 000000000..d333c22b7 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/examples/example-libevent.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + struct event_base *base = event_base_new(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibeventAttach(c,base); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + event_base_dispatch(base); + return 0; +} diff --git a/ext/hiredis-vip-0.3.0/examples/example-libuv.c b/ext/hiredis-vip-0.3.0/examples/example-libuv.c new file mode 100644 index 000000000..a5462d410 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/examples/example-libuv.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + uv_loop_t* loop = uv_default_loop(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibuvAttach(c,loop); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + uv_run(loop, UV_RUN_DEFAULT); + return 0; +} diff --git a/ext/hiredis-vip-0.3.0/examples/example.c b/ext/hiredis-vip-0.3.0/examples/example.c new file mode 100644 index 000000000..25226a807 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/examples/example.c @@ -0,0 +1,78 @@ +#include +#include +#include + +#include + +int main(int argc, char **argv) { + unsigned int j; + redisContext *c; + redisReply *reply; + const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; + int port = (argc > 2) ? atoi(argv[2]) : 6379; + + struct timeval timeout = { 1, 500000 }; // 1.5 seconds + c = redisConnectWithTimeout(hostname, port, timeout); + if (c == NULL || c->err) { + if (c) { + printf("Connection error: %s\n", c->errstr); + redisFree(c); + } else { + printf("Connection error: can't allocate redis context\n"); + } + exit(1); + } + + /* PING server */ + reply = redisCommand(c,"PING"); + printf("PING: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key */ + reply = redisCommand(c,"SET %s %s", "foo", "hello world"); + printf("SET: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key using binary safe API */ + reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); + printf("SET (binary API): %s\n", reply->str); + freeReplyObject(reply); + + /* Try a GET and two INCR */ + reply = redisCommand(c,"GET foo"); + printf("GET foo: %s\n", reply->str); + freeReplyObject(reply); + + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + /* again ... */ + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + + /* Create a list of numbers, from 0 to 9 */ + reply = redisCommand(c,"DEL mylist"); + freeReplyObject(reply); + for (j = 0; j < 10; j++) { + char buf[64]; + + snprintf(buf,64,"%d",j); + reply = redisCommand(c,"LPUSH mylist element-%s", buf); + freeReplyObject(reply); + } + + /* Let's check what we have inside the list */ + reply = redisCommand(c,"LRANGE mylist 0 -1"); + if (reply->type == REDIS_REPLY_ARRAY) { + for (j = 0; j < reply->elements; j++) { + printf("%u) %s\n", j, reply->element[j]->str); + } + } + freeReplyObject(reply); + + /* Disconnects and frees the context */ + redisFree(c); + + return 0; +} diff --git a/ext/hiredis-vip-0.3.0/fmacros.h b/ext/hiredis-vip-0.3.0/fmacros.h new file mode 100644 index 000000000..a3b1df034 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/fmacros.h @@ -0,0 +1,23 @@ +#ifndef __HIREDIS_FMACRO_H +#define __HIREDIS_FMACRO_H + +#if defined(__linux__) +#ifndef _BSD_SOURCE +#define _BSD_SOURCE +#endif +#define _DEFAULT_SOURCE +#endif + +#if defined(__sun__) +#define _POSIX_C_SOURCE 200112L +#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__) +#define _XOPEN_SOURCE 600 +#else +#define _XOPEN_SOURCE +#endif + +#if __APPLE__ && __MACH__ +#define _OSX +#endif + +#endif diff --git a/ext/hiredis-vip-0.3.0/hiarray.c b/ext/hiredis-vip-0.3.0/hiarray.c new file mode 100644 index 000000000..cf742ecf6 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hiarray.c @@ -0,0 +1,188 @@ +#include + +#include "hiutil.h" +#include "hiarray.h" + +struct hiarray * +hiarray_create(uint32_t n, size_t size) +{ + struct hiarray *a; + + ASSERT(n != 0 && size != 0); + + a = hi_alloc(sizeof(*a)); + if (a == NULL) { + return NULL; + } + + a->elem = hi_alloc(n * size); + if (a->elem == NULL) { + hi_free(a); + return NULL; + } + + a->nelem = 0; + a->size = size; + a->nalloc = n; + + return a; +} + +void +hiarray_destroy(struct hiarray *a) +{ + hiarray_deinit(a); + hi_free(a); +} + +int +hiarray_init(struct hiarray *a, uint32_t n, size_t size) +{ + ASSERT(n != 0 && size != 0); + + a->elem = hi_alloc(n * size); + if (a->elem == NULL) { + return HI_ENOMEM; + } + + a->nelem = 0; + a->size = size; + a->nalloc = n; + + return HI_OK; +} + +void +hiarray_deinit(struct hiarray *a) +{ + ASSERT(a->nelem == 0); + + if (a->elem != NULL) { + hi_free(a->elem); + } +} + +uint32_t +hiarray_idx(struct hiarray *a, void *elem) +{ + uint8_t *p, *q; + uint32_t off, idx; + + ASSERT(elem >= a->elem); + + p = a->elem; + q = elem; + off = (uint32_t)(q - p); + + ASSERT(off % (uint32_t)a->size == 0); + + idx = off / (uint32_t)a->size; + + return idx; +} + +void * +hiarray_push(struct hiarray *a) +{ + void *elem, *new; + size_t size; + + if (a->nelem == a->nalloc) { + + /* the array is full; allocate new array */ + size = a->size * a->nalloc; + new = hi_realloc(a->elem, 2 * size); + if (new == NULL) { + return NULL; + } + + a->elem = new; + a->nalloc *= 2; + } + + elem = (uint8_t *)a->elem + a->size * a->nelem; + a->nelem++; + + return elem; +} + +void * +hiarray_pop(struct hiarray *a) +{ + void *elem; + + ASSERT(a->nelem != 0); + + a->nelem--; + elem = (uint8_t *)a->elem + a->size * a->nelem; + + return elem; +} + +void * +hiarray_get(struct hiarray *a, uint32_t idx) +{ + void *elem; + + ASSERT(a->nelem != 0); + ASSERT(idx < a->nelem); + + elem = (uint8_t *)a->elem + (a->size * idx); + + return elem; +} + +void * +hiarray_top(struct hiarray *a) +{ + ASSERT(a->nelem != 0); + + return hiarray_get(a, a->nelem - 1); +} + +void +hiarray_swap(struct hiarray *a, struct hiarray *b) +{ + struct hiarray tmp; + + tmp = *a; + *a = *b; + *b = tmp; +} + +/* + * Sort nelem elements of the array in ascending order based on the + * compare comparator. + */ +void +hiarray_sort(struct hiarray *a, hiarray_compare_t compare) +{ + ASSERT(a->nelem != 0); + + qsort(a->elem, a->nelem, a->size, compare); +} + +/* + * Calls the func once for each element in the array as long as func returns + * success. On failure short-circuits and returns the error status. + */ +int +hiarray_each(struct hiarray *a, hiarray_each_t func, void *data) +{ + uint32_t i, nelem; + + ASSERT(array_n(a) != 0); + ASSERT(func != NULL); + + for (i = 0, nelem = hiarray_n(a); i < nelem; i++) { + void *elem = hiarray_get(a, i); + rstatus_t status; + + status = func(elem, data); + if (status != HI_OK) { + return status; + } + } + + return HI_OK; +} diff --git a/ext/hiredis-vip-0.3.0/hiarray.h b/ext/hiredis-vip-0.3.0/hiarray.h new file mode 100644 index 000000000..fda3a4b8b --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hiarray.h @@ -0,0 +1,56 @@ +#ifndef __HIARRAY_H_ +#define __HIARRAY_H_ + +#include + +typedef int (*hiarray_compare_t)(const void *, const void *); +typedef int (*hiarray_each_t)(void *, void *); + +struct hiarray { + uint32_t nelem; /* # element */ + void *elem; /* element */ + size_t size; /* element size */ + uint32_t nalloc; /* # allocated element */ +}; + +#define null_hiarray { 0, NULL, 0, 0 } + +static inline void +hiarray_null(struct hiarray *a) +{ + a->nelem = 0; + a->elem = NULL; + a->size = 0; + a->nalloc = 0; +} + +static inline void +hiarray_set(struct hiarray *a, void *elem, size_t size, uint32_t nalloc) +{ + a->nelem = 0; + a->elem = elem; + a->size = size; + a->nalloc = nalloc; +} + +static inline uint32_t +hiarray_n(const struct hiarray *a) +{ + return a->nelem; +} + +struct hiarray *hiarray_create(uint32_t n, size_t size); +void hiarray_destroy(struct hiarray *a); +int hiarray_init(struct hiarray *a, uint32_t n, size_t size); +void hiarray_deinit(struct hiarray *a); + +uint32_t hiarray_idx(struct hiarray *a, void *elem); +void *hiarray_push(struct hiarray *a); +void *hiarray_pop(struct hiarray *a); +void *hiarray_get(struct hiarray *a, uint32_t idx); +void *hiarray_top(struct hiarray *a); +void hiarray_swap(struct hiarray *a, struct hiarray *b); +void hiarray_sort(struct hiarray *a, hiarray_compare_t compare); +int hiarray_each(struct hiarray *a, hiarray_each_t func, void *data); + +#endif diff --git a/ext/hiredis-vip-0.3.0/hircluster.c b/ext/hiredis-vip-0.3.0/hircluster.c new file mode 100644 index 000000000..edf9cb2f9 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hircluster.c @@ -0,0 +1,4747 @@ + +#include "fmacros.h" +#include +#include +#include +#include +#include + +#include "hircluster.h" +#include "hiutil.h" +#include "adlist.h" +#include "hiarray.h" +#include "command.h" +#include "dict.c" + +#define REDIS_COMMAND_CLUSTER_NODES "CLUSTER NODES" +#define REDIS_COMMAND_CLUSTER_SLOTS "CLUSTER SLOTS" + +#define REDIS_COMMAND_ASKING "ASKING" +#define REDIS_COMMAND_PING "PING" + +#define REDIS_PROTOCOL_ASKING "*1\r\n$6\r\nASKING\r\n" + +#define IP_PORT_SEPARATOR ":" + +#define CLUSTER_ADDRESS_SEPARATOR "," + +#define CLUSTER_DEFAULT_MAX_REDIRECT_COUNT 5 + +typedef struct cluster_async_data +{ + redisClusterAsyncContext *acc; + struct cmd *command; + redisClusterCallbackFn *callback; + int retry_count; + void *privdata; +}cluster_async_data; + +typedef enum CLUSTER_ERR_TYPE{ + CLUSTER_NOT_ERR = 0, + CLUSTER_ERR_MOVED, + CLUSTER_ERR_ASK, + CLUSTER_ERR_TRYAGAIN, + CLUSTER_ERR_CROSSSLOT, + CLUSTER_ERR_CLUSTERDOWN, + CLUSTER_ERR_SENTINEL +}CLUSTER_ERR_TYPE; + +static void cluster_node_deinit(cluster_node *node); +static void cluster_slot_destroy(cluster_slot *slot); +static void cluster_open_slot_destroy(copen_slot *oslot); + +void listClusterNodeDestructor(void *val) +{ + cluster_node_deinit(val); + + hi_free(val); +} + +void listClusterSlotDestructor(void *val) +{ + cluster_slot_destroy(val); +} + +unsigned int dictSdsHash(const void *key) { + return dictGenHashFunction((unsigned char*)key, sdslen((char*)key)); +} + +int dictSdsKeyCompare(void *privdata, const void *key1, + const void *key2) +{ + int l1,l2; + DICT_NOTUSED(privdata); + + l1 = sdslen((sds)key1); + l2 = sdslen((sds)key2); + if (l1 != l2) return 0; + return memcmp(key1, key2, l1) == 0; +} + +void dictSdsDestructor(void *privdata, void *val) +{ + DICT_NOTUSED(privdata); + + sdsfree(val); +} + +void dictClusterNodeDestructor(void *privdata, void *val) +{ + DICT_NOTUSED(privdata); + + cluster_node_deinit(val); + + hi_free(val); +} + +/* Cluster nodes hash table, mapping nodes + * name(437c719f50dc9d0745032f3b280ce7ecc40792ac) + * or addresses(1.2.3.4:6379) to clusterNode structures. + * Those nodes need destroy. + */ +dictType clusterNodesDictType = { + dictSdsHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + dictClusterNodeDestructor /* val destructor */ +}; + +/* Cluster nodes hash table, mapping nodes + * name(437c719f50dc9d0745032f3b280ce7ecc40792ac) + * or addresses(1.2.3.4:6379) to clusterNode structures. + * Those nodes do not need destroy. + */ +dictType clusterNodesRefDictType = { + dictSdsHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + NULL /* val destructor */ +}; + + +void listCommandFree(void *command) +{ + struct cmd *cmd = command; + command_destroy(cmd); +} + +/* Defined in hiredis.c */ +void __redisSetError(redisContext *c, int type, const char *str); + +/* Forward declaration of function in hiredis.c */ +int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); + +/* Helper function for the redisClusterCommand* family of functions. + * + * Write a formatted command to the output buffer. If the given context is + * blocking, immediately read the reply into the "reply" pointer. When the + * context is non-blocking, the "reply" pointer will not be used and the + * command is simply appended to the write buffer. + * + * Returns the reply when a reply was succesfully retrieved. Returns NULL + * otherwise. When NULL is returned in a blocking context, the error field + * in the context will be set. + */ +static void *__redisBlockForReply(redisContext *c) { + void *reply; + + if (c->flags & REDIS_BLOCK) { + if (redisGetReply(c,&reply) != REDIS_OK) + return NULL; + return reply; + } + return NULL; +} + + +/* ----------------------------------------------------------------------------- + * Key space handling + * -------------------------------------------------------------------------- */ + +/* We have 16384 hash slots. The hash slot of a given key is obtained + * as the least significant 14 bits of the crc16 of the key. + * + * However if the key contains the {...} pattern, only the part between + * { and } is hashed. This may be useful in the future to force certain + * keys to be in the same node (assuming no resharding is in progress). */ +static unsigned int keyHashSlot(char *key, int keylen) { + int s, e; /* start-end indexes of { and } */ + + for (s = 0; s < keylen; s++) + if (key[s] == '{') break; + + /* No '{' ? Hash the whole key. This is the base case. */ + if (s == keylen) return crc16(key,keylen) & 0x3FFF; + + /* '{' found? Check if we have the corresponding '}'. */ + for (e = s+1; e < keylen; e++) + if (key[e] == '}') break; + + /* No '}' or nothing betweeen {} ? Hash the whole key. */ + if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF; + + /* If we are here there is both a { and a } on its right. Hash + * what is in the middle between { and }. */ + return crc16(key+s+1,e-s-1) & 0x3FFF; +} + +static void __redisClusterSetError(redisClusterContext *cc, int type, const char *str) { + size_t len; + + if(cc == NULL){ + return; + } + + cc->err = type; + if (str != NULL) { + len = strlen(str); + len = len < (sizeof(cc->errstr)-1) ? len : (sizeof(cc->errstr)-1); + memcpy(cc->errstr,str,len); + cc->errstr[len] = '\0'; + } else { + /* Only REDIS_ERR_IO may lack a description! */ + assert(type == REDIS_ERR_IO); + __redis_strerror_r(errno, cc->errstr, sizeof(cc->errstr)); + } +} + +static int cluster_reply_error_type(redisReply *reply) +{ + + if(reply == NULL) + { + return REDIS_ERR; + } + + if(reply->type == REDIS_REPLY_ERROR) + { + if((int)strlen(REDIS_ERROR_MOVED) < reply->len && + strncmp(reply->str, REDIS_ERROR_MOVED, strlen(REDIS_ERROR_MOVED)) == 0) + { + return CLUSTER_ERR_MOVED; + } + else if((int)strlen(REDIS_ERROR_ASK) < reply->len && + strncmp(reply->str, REDIS_ERROR_ASK, strlen(REDIS_ERROR_ASK)) == 0) + { + return CLUSTER_ERR_ASK; + } + else if((int)strlen(REDIS_ERROR_TRYAGAIN) < reply->len && + strncmp(reply->str, REDIS_ERROR_TRYAGAIN, strlen(REDIS_ERROR_TRYAGAIN)) == 0) + { + return CLUSTER_ERR_TRYAGAIN; + } + else if((int)strlen(REDIS_ERROR_CROSSSLOT) < reply->len && + strncmp(reply->str, REDIS_ERROR_CROSSSLOT, strlen(REDIS_ERROR_CROSSSLOT)) == 0) + { + return CLUSTER_ERR_CROSSSLOT; + } + else if((int)strlen(REDIS_ERROR_CLUSTERDOWN) < reply->len && + strncmp(reply->str, REDIS_ERROR_CLUSTERDOWN, strlen(REDIS_ERROR_CLUSTERDOWN)) == 0) + { + return CLUSTER_ERR_CLUSTERDOWN; + } + else + { + return CLUSTER_ERR_SENTINEL; + } + } + + return CLUSTER_NOT_ERR; +} + +static int cluster_node_init(cluster_node *node) +{ + if(node == NULL){ + return REDIS_ERR; + } + + node->name = NULL; + node->addr = NULL; + node->host = NULL; + node->port = 0; + node->role = REDIS_ROLE_NULL; + node->myself = 0; + node->slaves = NULL; + node->con = NULL; + node->acon = NULL; + node->slots = NULL; + node->failure_count = 0; + node->data = NULL; + node->migrating = NULL; + node->importing = NULL; + + return REDIS_OK; +} + +static void cluster_node_deinit(cluster_node *node) +{ + copen_slot **oslot; + + if(node == NULL) + { + return; + } + + sdsfree(node->name); + sdsfree(node->addr); + sdsfree(node->host); + node->port = 0; + node->role = REDIS_ROLE_NULL; + node->myself = 0; + + if(node->con != NULL) + { + redisFree(node->con); + } + + if(node->acon != NULL) + { + redisAsyncFree(node->acon); + } + + if(node->slots != NULL) + { + listRelease(node->slots); + } + + if(node->slaves != NULL) + { + listRelease(node->slaves); + } + + if(node->migrating) + { + while(hiarray_n(node->migrating)) + { + oslot = hiarray_pop(node->migrating); + cluster_open_slot_destroy(*oslot); + } + + hiarray_destroy(node->migrating); + node->migrating = NULL; + } + + if(node->importing) + { + while(hiarray_n(node->importing)) + { + oslot = hiarray_pop(node->importing); + cluster_open_slot_destroy(*oslot); + } + + hiarray_destroy(node->importing); + node->importing = NULL; + } +} + +static int cluster_slot_init(cluster_slot *slot, cluster_node *node) +{ + slot->start = 0; + slot->end = 0; + slot->node = node; + + return REDIS_OK; +} + +static cluster_slot *cluster_slot_create(cluster_node *node) +{ + cluster_slot *slot; + + slot = hi_alloc(sizeof(*slot)); + if(slot == NULL){ + return NULL; + } + + cluster_slot_init(slot, node); + + if(node != NULL){ + ASSERT(node->role == REDIS_ROLE_MASTER); + if(node->slots == NULL){ + node->slots = listCreate(); + if(node->slots == NULL) + { + cluster_slot_destroy(slot); + return NULL; + } + + node->slots->free = listClusterSlotDestructor; + } + + listAddNodeTail(node->slots, slot); + } + + return slot; +} + +static int cluster_slot_ref_node(cluster_slot * slot, cluster_node *node) +{ + if(slot == NULL || node == NULL){ + return REDIS_ERR; + } + + + if(node->role != REDIS_ROLE_MASTER){ + return REDIS_ERR; + } + + if(node->slots == NULL){ + node->slots = listCreate(); + if(node->slots == NULL) + { + return REDIS_ERR; + } + + node->slots->free = listClusterSlotDestructor; + } + + listAddNodeTail(node->slots, slot); + slot->node = node; + + return REDIS_OK; +} + +static void cluster_slot_destroy(cluster_slot *slot) +{ + slot->start = 0; + slot->end = 0; + slot->node = NULL; + + hi_free(slot); +} + +static copen_slot *cluster_open_slot_create(uint32_t slot_num, int migrate, + sds remote_name, cluster_node *node) +{ + copen_slot *oslot; + + oslot = hi_alloc(sizeof(*oslot)); + if(oslot == NULL){ + return NULL; + } + + oslot->slot_num = 0; + oslot->migrate = 0; + oslot->node = NULL; + oslot->remote_name = NULL; + + oslot->slot_num = slot_num; + oslot->migrate = migrate; + oslot->node = node; + oslot->remote_name = sdsdup(remote_name); + + return oslot; +} + +static void cluster_open_slot_destroy(copen_slot *oslot) +{ + oslot->slot_num = 0; + oslot->migrate = 0; + oslot->node = NULL; + + if(oslot->remote_name != NULL){ + sdsfree(oslot->remote_name); + oslot->remote_name = NULL; + } + + hi_free(oslot); +} + +/** + * Return a new node with the "cluster slots" command reply. + */ +static cluster_node *node_get_with_slots( + redisClusterContext *cc, redisReply *host_elem, + redisReply *port_elem, uint8_t role) +{ + cluster_node *node = NULL; + + if(host_elem == NULL || port_elem == NULL){ + return NULL; + } + + if(host_elem->type != REDIS_REPLY_STRING || + host_elem->len <= 0){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "node ip is not string."); + goto error; + } + + if(port_elem->type != REDIS_REPLY_INTEGER || + port_elem->integer <= 0){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "node port is not integer."); + goto error; + } + + if(!hi_valid_port((int)port_elem->integer)){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "node port is not valid."); + goto error; + } + + node = hi_alloc(sizeof(cluster_node)); + if(node == NULL){ + __redisClusterSetError(cc, + REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + cluster_node_init(node); + + if(role == REDIS_ROLE_MASTER){ + node->slots = listCreate(); + if(node->slots == NULL){ + hi_free(node); + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "slots for node listCreate error"); + goto error; + } + + node->slots->free = listClusterSlotDestructor; + } + + node->name = NULL; + node->addr = sdsnewlen(host_elem->str, host_elem->len); + node->addr = sdscatfmt(node->addr, ":%i", port_elem->integer); + + node->host = sdsnewlen(host_elem->str, host_elem->len); + node->port = (int)port_elem->integer; + node->role = role; + + return node; + +error: + + if(node != NULL){ + hi_free(node); + } + + return NULL; +} + +/** + * Return a new node with the "cluster nodes" command reply. + */ +static cluster_node *node_get_with_nodes( + redisClusterContext *cc, + sds *node_infos, int info_count, uint8_t role) +{ + sds *ip_port = NULL; + int count_ip_port = 0; + cluster_node *node; + + if(info_count < 8) + { + return NULL; + } + + node = hi_alloc(sizeof(cluster_node)); + if(node == NULL) + { + __redisClusterSetError(cc, + REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + cluster_node_init(node); + + if(role == REDIS_ROLE_MASTER) + { + node->slots = listCreate(); + if(node->slots == NULL) + { + hi_free(node); + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "slots for node listCreate error"); + goto error; + } + + node->slots->free = listClusterSlotDestructor; + } + + node->name = node_infos[0]; + node->addr = node_infos[1]; + + ip_port = sdssplitlen(node_infos[1], sdslen(node_infos[1]), + IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &count_ip_port); + if(ip_port == NULL || count_ip_port != 2) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "split ip port error"); + goto error; + } + node->host = ip_port[0]; + node->port = hi_atoi(ip_port[1], sdslen(ip_port[1])); + node->role = role; + + sdsfree(ip_port[1]); + free(ip_port); + + node_infos[0] = NULL; + node_infos[1] = NULL; + + return node; + +error: + if(ip_port != NULL) + { + sdsfreesplitres(ip_port, count_ip_port); + } + + if(node != NULL) + { + hi_free(node); + } + + return NULL; +} + +static void cluster_nodes_swap_ctx(dict *nodes_f, dict *nodes_t) +{ + dictIterator *di; + dictEntry *de_f, *de_t; + cluster_node *node_f, *node_t; + redisContext *c; + redisAsyncContext *ac; + + if(nodes_f == NULL || nodes_t == NULL){ + return; + } + + di = dictGetIterator(nodes_t); + while((de_t = dictNext(di)) != NULL){ + node_t = dictGetEntryVal(de_t); + if(node_t == NULL){ + continue; + } + + de_f = dictFind(nodes_f, node_t->addr); + if(de_f == NULL){ + continue; + } + + node_f = dictGetEntryVal(de_f); + if(node_f->con != NULL){ + c = node_f->con; + node_f->con = node_t->con; + node_t->con = c; + } + + if(node_f->acon != NULL){ + ac = node_f->acon; + node_f->acon = node_t->acon; + node_t->acon = ac; + + node_t->acon->data = node_t; + if (node_f->acon) + node_f->acon->data = node_f; + } + } + + dictReleaseIterator(di); + +} + +static int +cluster_slot_start_cmp(const void *t1, const void *t2) +{ + const cluster_slot **s1 = t1, **s2 = t2; + + return (*s1)->start > (*s2)->start?1:-1; +} + +static int +cluster_master_slave_mapping_with_name(redisClusterContext *cc, + dict **nodes, cluster_node *node, sds master_name) +{ + int ret; + dictEntry *di; + cluster_node *node_old; + listNode *lnode; + + if(node == NULL || master_name == NULL) + { + return REDIS_ERR; + } + + if(*nodes == NULL) + { + *nodes = dictCreate( + &clusterNodesRefDictType, NULL); + } + + di = dictFind(*nodes, master_name); + if(di == NULL) + { + ret = dictAdd(*nodes, + sdsnewlen(master_name, sdslen(master_name)), node); + if(ret != DICT_OK) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "the address already exists in the nodes"); + return REDIS_ERR; + } + + } + else + { + node_old = dictGetEntryVal(di); + if(node_old == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "dict get value null"); + return REDIS_ERR; + } + + if(node->role == REDIS_ROLE_MASTER && + node_old->role == REDIS_ROLE_MASTER) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "two masters have the same name"); + return REDIS_ERR; + } + else if(node->role == REDIS_ROLE_MASTER + && node_old->role == REDIS_ROLE_SLAVE) + { + if(node->slaves == NULL) + { + node->slaves = listCreate(); + if(node->slaves == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Out of memory"); + return REDIS_ERR; + } + + node->slaves->free = + listClusterNodeDestructor; + } + + if(node_old->slaves != NULL) + { + node_old->slaves->free = NULL; + while(listLength(node_old->slaves) > 0) + { + lnode = listFirst(node_old->slaves); + listAddNodeHead(node->slaves, lnode->value); + listDelNode(node_old->slaves, lnode); + } + listRelease(node_old->slaves); + node_old->slaves = NULL; + } + + listAddNodeHead(node->slaves, node_old); + + dictSetHashVal(*nodes, di, node); + } + else if(node->role == REDIS_ROLE_SLAVE) + { + if(node_old->slaves == NULL) + { + node_old->slaves = listCreate(); + if(node_old->slaves == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Out of memory"); + return REDIS_ERR; + } + + node_old->slaves->free = + listClusterNodeDestructor; + } + + listAddNodeTail(node_old->slaves, node); + } + else + { + NOT_REACHED(); + } + } + + return REDIS_OK; +} + +/** + * Parse the "cluster slots" command reply to nodes dict. + */ +dict * +parse_cluster_slots(redisClusterContext *cc, + redisReply *reply, int flags) +{ + int ret; + cluster_slot *slot = NULL; + dict *nodes = NULL; + dictEntry *den; + redisReply *elem_slots; + redisReply *elem_slots_begin, *elem_slots_end; + redisReply *elem_nodes; + redisReply *elem_ip, *elem_port; + cluster_node *master = NULL, *slave; + sds address; + uint32_t i, idx; + + if(reply == NULL){ + return NULL; + } + + nodes = dictCreate(&clusterNodesDictType, NULL); + if(nodes == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OOM, + "out of memory"); + goto error; + } + + if(reply->type != REDIS_REPLY_ARRAY || reply->elements <= 0){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "reply is not an array."); + goto error; + } + + for(i = 0; i < reply->elements; i ++){ + elem_slots = reply->element[i]; + if(elem_slots->type != REDIS_REPLY_ARRAY || + elem_slots->elements < 3){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "first sub_reply is not an array."); + goto error; + } + + slot = cluster_slot_create(NULL); + if(slot == NULL){ + __redisClusterSetError(cc, REDIS_ERR_OOM, + "Slot create failed: out of memory."); + goto error; + } + + //one slots region + for(idx = 0; idx < elem_slots->elements; idx ++){ + if(idx == 0){ + elem_slots_begin = elem_slots->element[idx]; + if(elem_slots_begin->type != REDIS_REPLY_INTEGER){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "slot begin is not an integer."); + goto error; + } + slot->start = (int)(elem_slots_begin->integer); + }else if(idx == 1){ + elem_slots_end = elem_slots->element[idx]; + if(elem_slots_end->type != REDIS_REPLY_INTEGER){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "slot end is not an integer."); + goto error; + } + + slot->end = (int)(elem_slots_end->integer); + + if(slot->start > slot->end){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "slot begin is bigger than slot end."); + goto error; + } + }else{ + elem_nodes = elem_slots->element[idx]; + if(elem_nodes->type != REDIS_REPLY_ARRAY || + elem_nodes->elements != 3){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "nodes sub_reply is not an correct array."); + goto error; + } + + elem_ip = elem_nodes->element[0]; + elem_port = elem_nodes->element[1]; + + if(elem_ip == NULL || elem_port == NULL || + elem_ip->type != REDIS_REPLY_STRING || + elem_port->type != REDIS_REPLY_INTEGER){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "master ip or port is not correct."); + goto error; + } + + //this is master. + if(idx == 2){ + address = sdsnewlen(elem_ip->str, elem_ip->len); + address = sdscatfmt(address, ":%i", elem_port->integer); + + den = dictFind(nodes, address); + //master already exits, break to the next slots region. + if(den != NULL){ + sdsfree(address); + + master = dictGetEntryVal(den); + ret = cluster_slot_ref_node(slot, master); + if(ret != REDIS_OK){ + __redisClusterSetError(cc, REDIS_ERR_OOM, + "Slot ref node failed: out of memory."); + goto error; + } + + slot = NULL; + break; + } + + sdsfree(address); + master = node_get_with_slots(cc, elem_ip, + elem_port, REDIS_ROLE_MASTER); + if(master == NULL){ + goto error; + } + + ret = dictAdd(nodes, + sdsnewlen(master->addr, sdslen(master->addr)), master); + if(ret != DICT_OK){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "The address already exists in the nodes"); + cluster_node_deinit(master); + hi_free(master); + goto error; + } + + ret = cluster_slot_ref_node(slot, master); + if(ret != REDIS_OK){ + __redisClusterSetError(cc, REDIS_ERR_OOM, + "Slot ref node failed: out of memory."); + goto error; + } + + slot = NULL; + }else if(flags & HIRCLUSTER_FLAG_ADD_SLAVE){ + slave = node_get_with_slots(cc, elem_ip, + elem_port, REDIS_ROLE_SLAVE); + if(slave == NULL){ + goto error; + } + + if(master->slaves == NULL){ + master->slaves = listCreate(); + if(master->slaves == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Out of memory"); + cluster_node_deinit(slave); + goto error; + } + + master->slaves->free = + listClusterNodeDestructor; + } + + listAddNodeTail(master->slaves, slave); + } + } + } + } + + return nodes; + +error: + + if(nodes != NULL){ + dictRelease(nodes); + } + + if(slot != NULL){ + cluster_slot_destroy(slot); + } + + return NULL; +} + +/** + * Parse the "cluster nodes" command reply to nodes dict. + */ +dict * +parse_cluster_nodes(redisClusterContext *cc, + char *str, int str_len, int flags) +{ + int ret; + dict *nodes = NULL; + dict *nodes_name = NULL; + cluster_node *master, *slave; + cluster_slot *slot; + char *pos, *start, *end, *line_start, *line_end; + char *role; + int role_len; + uint8_t myself = 0; + int slot_start, slot_end; + sds *part = NULL, *slot_start_end = NULL; + int count_part = 0, count_slot_start_end = 0; + int k; + int len; + + nodes = dictCreate(&clusterNodesDictType, NULL); + if(nodes == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OOM, + "out of memory"); + goto error; + } + + start = str; + end = start + str_len; + + line_start = start; + + for(pos = start; pos < end; pos ++){ + if(*pos == '\n'){ + line_end = pos - 1; + len = line_end - line_start; + + part = sdssplitlen(line_start, len + 1, " ", 1, &count_part); + + if(part == NULL || count_part < 8){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "split cluster nodes error"); + goto error; + } + + //the address string is ":0", skip this node. + if(sdslen(part[1]) == 2 && strcmp(part[1], ":0") == 0){ + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + + start = pos + 1; + line_start = start; + pos = start; + + continue; + } + + if(sdslen(part[2]) >= 7 && memcmp(part[2], "myself,", 7) == 0){ + role_len = sdslen(part[2]) - 7; + role = part[2] + 7; + myself = 1; + }else{ + role_len = sdslen(part[2]); + role = part[2]; + } + + //add master node + if(role_len >= 6 && memcmp(role, "master", 6) == 0){ + if(count_part < 8){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Master node parts number error: less than 8."); + goto error; + } + + master = node_get_with_nodes(cc, + part, count_part, REDIS_ROLE_MASTER); + if(master == NULL){ + goto error; + } + + ret = dictAdd(nodes, + sdsnewlen(master->addr, sdslen(master->addr)), master); + if(ret != DICT_OK){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "The address already exists in the nodes"); + cluster_node_deinit(master); + hi_free(master); + goto error; + } + + if(flags & HIRCLUSTER_FLAG_ADD_SLAVE){ + ret = cluster_master_slave_mapping_with_name(cc, + &nodes_name, master, master->name); + if(ret != REDIS_OK){ + cluster_node_deinit(master); + hi_free(master); + goto error; + } + } + + if(myself) master->myself = 1; + + for(k = 8; k < count_part; k ++){ + slot_start_end = sdssplitlen(part[k], + sdslen(part[k]), "-", 1, &count_slot_start_end); + + if(slot_start_end == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "split slot start end error(NULL)"); + goto error; + }else if(count_slot_start_end == 1){ + slot_start = + hi_atoi(slot_start_end[0], sdslen(slot_start_end[0])); + slot_end = slot_start; + }else if(count_slot_start_end == 2){ + slot_start = + hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));; + slot_end = + hi_atoi(slot_start_end[1], sdslen(slot_start_end[1]));; + }else{ + //add open slot for master + if(flags & HIRCLUSTER_FLAG_ADD_OPENSLOT && + count_slot_start_end == 3 && + sdslen(slot_start_end[0]) > 1 && + sdslen(slot_start_end[1]) == 1 && + sdslen(slot_start_end[2]) > 1 && + slot_start_end[0][0] == '[' && + slot_start_end[2][sdslen(slot_start_end[2])-1] == ']'){ + + copen_slot *oslot, **oslot_elem; + + sdsrange(slot_start_end[0], 1, -1); + sdsrange(slot_start_end[2], 0, -2); + + if(slot_start_end[1][0] == '>'){ + oslot = cluster_open_slot_create( + hi_atoi(slot_start_end[0], + sdslen(slot_start_end[0])), + 1, slot_start_end[2], master); + if(oslot == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "create open slot error"); + goto error; + } + + if(master->migrating == NULL){ + master->migrating = hiarray_create(1, sizeof(oslot)); + if(master->migrating == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "create migrating array error"); + cluster_open_slot_destroy(oslot); + goto error; + } + } + + oslot_elem = hiarray_push(master->migrating); + if(oslot_elem == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Push migrating array error: out of memory"); + cluster_open_slot_destroy(oslot); + goto error; + } + + *oslot_elem = oslot; + }else if(slot_start_end[1][0] == '<'){ + oslot = cluster_open_slot_create(hi_atoi(slot_start_end[0], + sdslen(slot_start_end[0])), 0, slot_start_end[2], + master); + if(oslot == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "create open slot error"); + goto error; + } + + if(master->importing == NULL){ + master->importing = hiarray_create(1, sizeof(oslot)); + if(master->importing == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "create migrating array error"); + cluster_open_slot_destroy(oslot); + goto error; + } + } + + oslot_elem = hiarray_push(master->importing); + if(oslot_elem == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "push migrating array error: out of memory"); + cluster_open_slot_destroy(oslot); + goto error; + } + + *oslot_elem = oslot; + } + } + + slot_start = -1; + slot_end = -1; + } + + sdsfreesplitres(slot_start_end, count_slot_start_end); + count_slot_start_end = 0; + slot_start_end = NULL; + + if(slot_start < 0 || slot_end < 0 || + slot_start > slot_end || slot_end >= REDIS_CLUSTER_SLOTS){ + continue; + } + + slot = cluster_slot_create(master); + if(slot == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Out of memory"); + goto error; + } + + slot->start = (uint32_t)slot_start; + slot->end = (uint32_t)slot_end; + } + + } + //add slave node + else if((flags & HIRCLUSTER_FLAG_ADD_SLAVE) && + (role_len >= 5 && memcmp(role, "slave", 5) == 0)){ + slave = node_get_with_nodes(cc, part, + count_part, REDIS_ROLE_SLAVE); + if(slave == NULL){ + goto error; + } + + ret = cluster_master_slave_mapping_with_name(cc, + &nodes_name, slave, part[3]); + if(ret != REDIS_OK){ + cluster_node_deinit(slave); + hi_free(slave); + goto error; + } + + if(myself) slave->myself = 1; + } + + if(myself == 1){ + myself = 0; + } + + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + + start = pos + 1; + line_start = start; + pos = start; + } + } + + if(nodes_name != NULL){ + dictRelease(nodes_name); + } + + return nodes; + +error: + + if(part != NULL){ + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + } + + if(slot_start_end != NULL){ + sdsfreesplitres(slot_start_end, count_slot_start_end); + count_slot_start_end = 0; + slot_start_end = NULL; + } + + if(nodes != NULL){ + dictRelease(nodes); + } + + if(nodes_name != NULL){ + dictRelease(nodes_name); + } + + return NULL; +} + +/** + * Update route with the "cluster nodes" or "cluster slots" command reply. + */ +static int +cluster_update_route_by_addr(redisClusterContext *cc, + const char *ip, int port) +{ + redisContext *c = NULL; + redisReply *reply = NULL; + dict *nodes = NULL; + struct hiarray *slots = NULL; + cluster_node *master; + cluster_slot *slot, **slot_elem; + dictIterator *dit = NULL; + dictEntry *den; + listIter *lit = NULL; + listNode *lnode; + cluster_node *table[REDIS_CLUSTER_SLOTS]; + uint32_t j, k; + + if(cc == NULL){ + return REDIS_ERR; + } + + if(ip == NULL || port <= 0){ + __redisClusterSetError(cc, + REDIS_ERR_OTHER,"Ip or port error!"); + goto error; + } + + if(cc->timeout){ + c = redisConnectWithTimeout(ip, port, *cc->timeout); + }else{ + c = redisConnect(ip, port); + } + + if (c == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Init redis context error(return NULL)"); + goto error; + }else if(c->err){ + __redisClusterSetError(cc,c->err,c->errstr); + goto error; + } + + if(cc->flags & HIRCLUSTER_FLAG_ROUTE_USE_SLOTS){ + reply = redisCommand(c, REDIS_COMMAND_CLUSTER_SLOTS); + if(reply == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Command(cluster slots) reply error(NULL)."); + goto error; + }else if(reply->type != REDIS_REPLY_ARRAY){ + if(reply->type == REDIS_REPLY_ERROR){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + reply->str); + }else{ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Command(cluster slots) reply error: type is not array."); + } + + goto error; + } + + nodes = parse_cluster_slots(cc, reply, cc->flags); + }else{ + reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES); + if(reply == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Command(cluster nodes) reply error(NULL)."); + goto error; + }else if(reply->type != REDIS_REPLY_STRING){ + if(reply->type == REDIS_REPLY_ERROR){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + reply->str); + }else{ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Command(cluster nodes) reply error: type is not string."); + } + + goto error; + } + + nodes = parse_cluster_nodes(cc, reply->str, reply->len, cc->flags); + } + + if(nodes == NULL){ + goto error; + } + + memset(table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + + slots = hiarray_create(dictSize(nodes), sizeof(cluster_slot*)); + if(slots == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Slots array create failed: out of memory"); + goto error; + } + + dit = dictGetIterator(nodes); + if(dit == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Dict get iterator failed: out of memory"); + goto error; + } + + while((den = dictNext(dit))){ + master = dictGetEntryVal(den); + if(master->role != REDIS_ROLE_MASTER){ + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Node role must be master"); + goto error; + } + + if(master->slots == NULL){ + continue; + } + + lit = listGetIterator(master->slots, AL_START_HEAD); + if(lit == NULL){ + __redisClusterSetError(cc, REDIS_ERR_OOM, + "List get iterator failed: out of memory"); + goto error; + } + + while((lnode = listNext(lit))){ + slot = listNodeValue(lnode); + if(slot->start > slot->end || + slot->end >= REDIS_CLUSTER_SLOTS){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Slot region for node is error"); + goto error; + } + + slot_elem = hiarray_push(slots); + *slot_elem = slot; + } + + listReleaseIterator(lit); + } + + dictReleaseIterator(dit); + + hiarray_sort(slots, cluster_slot_start_cmp); + for(j = 0; j < hiarray_n(slots); j ++){ + slot_elem = hiarray_get(slots, j); + + for(k = (*slot_elem)->start; k <= (*slot_elem)->end; k ++){ + if(table[k] != NULL){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Diffent node hold a same slot"); + goto error; + } + + table[k] = (*slot_elem)->node; + } + } + + cluster_nodes_swap_ctx(cc->nodes, nodes); + if(cc->nodes != NULL){ + dictRelease(cc->nodes); + cc->nodes = NULL; + } + cc->nodes = nodes; + + if(cc->slots != NULL) + { + cc->slots->nelem = 0; + hiarray_destroy(cc->slots); + cc->slots = NULL; + } + cc->slots = slots; + + memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + cc->route_version ++; + + freeReplyObject(reply); + + if(c != NULL){ + redisFree(c); + } + + return REDIS_OK; + +error: + + if(dit != NULL){ + dictReleaseIterator(dit); + } + + if(lit != NULL){ + listReleaseIterator(lit); + } + + if(slots != NULL) + { + if(slots == cc->slots) + { + cc->slots = NULL; + } + + slots->nelem = 0; + hiarray_destroy(slots); + } + + if(nodes != NULL){ + if(nodes == cc->nodes){ + cc->nodes = NULL; + } + + dictRelease(nodes); + } + + if(reply != NULL){ + freeReplyObject(reply); + reply = NULL; + } + + if(c != NULL){ + redisFree(c); + } + + return REDIS_ERR; +} + + +/** + * Update route with the "cluster nodes" command reply. + */ +static int +cluster_update_route_with_nodes_old(redisClusterContext *cc, + const char *ip, int port) +{ + int ret; + redisContext *c = NULL; + redisReply *reply = NULL; + struct hiarray *slots = NULL; + dict *nodes = NULL; + dict *nodes_name = NULL; + cluster_node *master, *slave; + cluster_slot **slot; + char *pos, *start, *end, *line_start, *line_end; + char *role; + int role_len; + uint8_t myself = 0; + int slot_start, slot_end; + sds *part = NULL, *slot_start_end = NULL; + int count_part = 0, count_slot_start_end = 0; + int j, k; + int len; + cluster_node *table[REDIS_CLUSTER_SLOTS] = {NULL}; + + if(cc == NULL) + { + return REDIS_ERR; + } + + if(ip == NULL || port <= 0) + { + __redisClusterSetError(cc, + REDIS_ERR_OTHER,"ip or port error!"); + goto error; + } + + if(cc->timeout) + { + c = redisConnectWithTimeout(ip, port, *cc->timeout); + } + else + { + c = redisConnect(ip, port); + } + + if (c == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "init redis context error(return NULL)"); + goto error; + } + else if(c->err) + { + __redisClusterSetError(cc,c->err,c->errstr); + goto error; + } + + reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES); + + if(reply == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "command(cluster nodes) reply error(NULL)"); + goto error; + } + else if(reply->type != REDIS_REPLY_STRING) + { + if(reply->type == REDIS_REPLY_ERROR) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + reply->str); + } + else + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "command(cluster nodes) reply error(type is not string)"); + } + + goto error; + } + + nodes = dictCreate(&clusterNodesDictType, NULL); + + slots = hiarray_create(10, sizeof(cluster_slot*)); + if(slots == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "array create error"); + goto error; + } + + start = reply->str; + end = start + reply->len; + + line_start = start; + + for(pos = start; pos < end; pos ++) + { + if(*pos == '\n') + { + line_end = pos - 1; + len = line_end - line_start; + + part = sdssplitlen(line_start, len + 1, " ", 1, &count_part); + + if(part == NULL || count_part < 8) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "split cluster nodes error"); + goto error; + } + + //the address string is ":0", skip this node. + if(sdslen(part[1]) == 2 && strcmp(part[1], ":0") == 0) + { + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + + start = pos + 1; + line_start = start; + pos = start; + + continue; + } + + if(sdslen(part[2]) >= 7 && memcmp(part[2], "myself,", 7) == 0) + { + role_len = sdslen(part[2]) - 7; + role = part[2] + 7; + myself = 1; + } + else + { + role_len = sdslen(part[2]); + role = part[2]; + } + + //add master node + if(role_len >= 6 && memcmp(role, "master", 6) == 0) + { + if(count_part < 8) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "master node part number error"); + goto error; + } + + master = node_get_with_nodes(cc, + part, count_part, REDIS_ROLE_MASTER); + if(master == NULL) + { + goto error; + } + + ret = dictAdd(nodes, + sdsnewlen(master->addr, sdslen(master->addr)), master); + if(ret != DICT_OK) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "the address already exists in the nodes"); + cluster_node_deinit(master); + hi_free(master); + goto error; + } + + if(cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE) + { + ret = cluster_master_slave_mapping_with_name(cc, + &nodes_name, master, master->name); + if(ret != REDIS_OK) + { + cluster_node_deinit(master); + hi_free(master); + goto error; + } + } + + if(myself == 1) + { + master->con = c; + c = NULL; + } + + for(k = 8; k < count_part; k ++) + { + slot_start_end = sdssplitlen(part[k], + sdslen(part[k]), "-", 1, &count_slot_start_end); + + if(slot_start_end == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "split slot start end error(NULL)"); + goto error; + } + else if(count_slot_start_end == 1) + { + slot_start = + hi_atoi(slot_start_end[0], sdslen(slot_start_end[0])); + slot_end = slot_start; + } + else if(count_slot_start_end == 2) + { + slot_start = + hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));; + slot_end = + hi_atoi(slot_start_end[1], sdslen(slot_start_end[1]));; + } + else + { + slot_start = -1; + slot_end = -1; + } + + sdsfreesplitres(slot_start_end, count_slot_start_end); + count_slot_start_end = 0; + slot_start_end = NULL; + + if(slot_start < 0 || slot_end < 0 || + slot_start > slot_end || slot_end >= REDIS_CLUSTER_SLOTS) + { + continue; + } + + for(j = slot_start; j <= slot_end; j ++) + { + if(table[j] != NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "diffent node hold a same slot"); + goto error; + } + table[j] = master; + } + + slot = hiarray_push(slots); + if(slot == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "slot push in array error"); + goto error; + } + + *slot = cluster_slot_create(master); + if(*slot == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Out of memory"); + goto error; + } + + (*slot)->start = (uint32_t)slot_start; + (*slot)->end = (uint32_t)slot_end; + } + + } + //add slave node + else if((cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE) && + (role_len >= 5 && memcmp(role, "slave", 5) == 0)) + { + slave = node_get_with_nodes(cc, part, + count_part, REDIS_ROLE_SLAVE); + if(slave == NULL) + { + goto error; + } + + ret = cluster_master_slave_mapping_with_name(cc, + &nodes_name, slave, part[3]); + if(ret != REDIS_OK) + { + cluster_node_deinit(slave); + hi_free(slave); + goto error; + } + + if(myself == 1) + { + slave->con = c; + c = NULL; + } + } + + if(myself == 1) + { + myself = 0; + } + + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + + start = pos + 1; + line_start = start; + pos = start; + } + } + + if(cc->slots != NULL) + { + cc->slots->nelem = 0; + hiarray_destroy(cc->slots); + cc->slots = NULL; + } + cc->slots = slots; + + cluster_nodes_swap_ctx(cc->nodes, nodes); + + if(cc->nodes != NULL) + { + dictRelease(cc->nodes); + cc->nodes = NULL; + } + cc->nodes = nodes; + + hiarray_sort(cc->slots, cluster_slot_start_cmp); + + memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + cc->route_version ++; + + freeReplyObject(reply); + + if(c != NULL) + { + redisFree(c); + } + + if(nodes_name != NULL) + { + dictRelease(nodes_name); + } + + return REDIS_OK; + +error: + + if(part != NULL) + { + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + } + + if(slot_start_end != NULL) + { + sdsfreesplitres(slot_start_end, count_slot_start_end); + count_slot_start_end = 0; + slot_start_end = NULL; + } + + if(slots != NULL) + { + if(slots == cc->slots) + { + cc->slots = NULL; + } + + slots->nelem = 0; + hiarray_destroy(slots); + } + + if(nodes != NULL) + { + if(nodes == cc->nodes) + { + cc->nodes = NULL; + } + + dictRelease(nodes); + } + + if(nodes_name != NULL) + { + dictRelease(nodes_name); + } + + if(reply != NULL) + { + freeReplyObject(reply); + reply = NULL; + } + + if(c != NULL) + { + redisFree(c); + } + + return REDIS_ERR; +} + +int +cluster_update_route(redisClusterContext *cc) +{ + int ret; + int flag_err_not_set = 1; + cluster_node *node; + dictIterator *it; + dictEntry *de; + + if(cc == NULL) + { + return REDIS_ERR; + } + + if(cc->ip != NULL && cc->port > 0) + { + ret = cluster_update_route_by_addr(cc, cc->ip, cc->port); + if(ret == REDIS_OK) + { + return REDIS_OK; + } + + flag_err_not_set = 0; + } + + if(cc->nodes == NULL) + { + if(flag_err_not_set) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "no server address"); + } + + return REDIS_ERR; + } + + it = dictGetIterator(cc->nodes); + while ((de = dictNext(it)) != NULL) + { + node = dictGetEntryVal(de); + if(node == NULL || node->host == NULL || node->port < 0) + { + continue; + } + + ret = cluster_update_route_by_addr(cc, node->host, node->port); + if(ret == REDIS_OK) + { + if(cc->err) + { + cc->err = 0; + memset(cc->errstr, '\0', strlen(cc->errstr)); + } + + dictReleaseIterator(it); + return REDIS_OK; + } + + flag_err_not_set = 0; + } + + dictReleaseIterator(it); + + if(flag_err_not_set) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "no valid server address"); + } + + return REDIS_ERR; +} + +static void print_cluster_node_list(redisClusterContext *cc) +{ + dictIterator *di = NULL; + dictEntry *de; + listIter *it; + listNode *ln; + cluster_node *master, *slave; + hilist *slaves; + + if(cc == NULL) + { + return; + } + + di = dictGetIterator(cc->nodes); + + printf("name\taddress\trole\tslaves\n"); + + while((de = dictNext(di)) != NULL) { + master = dictGetEntryVal(de); + + printf("%s\t%s\t%d\t%s\n",master->name, master->addr, + master->role, master->slaves?"hava":"null"); + + slaves = master->slaves; + if(slaves == NULL) + { + continue; + } + + it = listGetIterator(slaves, AL_START_HEAD); + while((ln = listNext(it)) != NULL) + { + slave = listNodeValue(ln); + printf("%s\t%s\t%d\t%s\n",slave->name, slave->addr, + slave->role, slave->slaves?"hava":"null"); + } + + listReleaseIterator(it); + + printf("\n"); + } +} + + +int test_cluster_update_route(redisClusterContext *cc) +{ + int ret; + + ret = cluster_update_route(cc); + + //print_cluster_node_list(cc); + + return ret; +} + +static redisClusterContext *redisClusterContextInit(void) { + redisClusterContext *cc; + + cc = calloc(1,sizeof(redisClusterContext)); + if (cc == NULL) + return NULL; + + cc->err = 0; + cc->errstr[0] = '\0'; + cc->ip = NULL; + cc->port = 0; + cc->flags = 0; + cc->timeout = NULL; + cc->nodes = NULL; + cc->slots = NULL; + cc->max_redirect_count = CLUSTER_DEFAULT_MAX_REDIRECT_COUNT; + cc->retry_count = 0; + cc->requests = NULL; + cc->need_update_route = 0; + cc->update_route_time = 0LL; + + cc->route_version = 0LL; + + memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + + return cc; +} + +void redisClusterFree(redisClusterContext *cc) { + + if (cc == NULL) + return; + + if(cc->ip) + { + sdsfree(cc->ip); + cc->ip = NULL; + } + + if (cc->timeout) + { + free(cc->timeout); + } + + memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + + if(cc->slots != NULL) + { + cc->slots->nelem = 0; + hiarray_destroy(cc->slots); + cc->slots = NULL; + } + + if(cc->nodes != NULL) + { + dictRelease(cc->nodes); + } + + if(cc->requests != NULL) + { + listRelease(cc->requests); + } + + free(cc); +} + +static int redisClusterAddNode(redisClusterContext *cc, const char *addr) +{ + dictEntry *node_entry; + cluster_node *node; + sds *ip_port = NULL; + int ip_port_count = 0; + sds ip; + int port; + + if(cc == NULL) + { + return REDIS_ERR; + } + + if(cc->nodes == NULL) + { + cc->nodes = dictCreate(&clusterNodesDictType, NULL); + if(cc->nodes == NULL) + { + return REDIS_ERR; + } + } + + node_entry = dictFind(cc->nodes, addr); + if(node_entry == NULL) + { + ip_port = sdssplitlen(addr, strlen(addr), + IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &ip_port_count); + if(ip_port == NULL || ip_port_count != 2 || + sdslen(ip_port[0]) <= 0 || sdslen(ip_port[1]) <= 0) + { + if(ip_port != NULL) + { + sdsfreesplitres(ip_port, ip_port_count); + } + __redisClusterSetError(cc,REDIS_ERR_OTHER,"server address is error(correct is like: 127.0.0.1:1234)"); + return REDIS_ERR; + } + + ip = ip_port[0]; + port = hi_atoi(ip_port[1], sdslen(ip_port[1])); + + if(port <= 0) + { + sdsfreesplitres(ip_port, ip_port_count); + __redisClusterSetError(cc,REDIS_ERR_OTHER,"server port is error"); + return REDIS_ERR; + } + + sdsfree(ip_port[1]); + free(ip_port); + ip_port = NULL; + + node = hi_alloc(sizeof(cluster_node)); + if(node == NULL) + { + sdsfree(ip); + __redisClusterSetError(cc,REDIS_ERR_OTHER,"alloc cluster node error"); + return REDIS_ERR; + } + + cluster_node_init(node); + + node->addr = sdsnew(addr); + if(node->addr == NULL) + { + sdsfree(ip); + hi_free(node); + __redisClusterSetError(cc,REDIS_ERR_OTHER,"new node address error"); + return REDIS_ERR; + } + + node->host = ip; + node->port = port; + + dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); + } + + return REDIS_OK; +} + + +/* Connect to a Redis cluster. On error the field error in the returned + * context will be set to the return value of the error function. + * When no set of reply functions is given, the default set will be used. */ +static redisClusterContext *_redisClusterConnect(redisClusterContext *cc, const char *addrs) { + + int ret; + sds *address = NULL; + int address_count = 0; + int i; + + if(cc == NULL) + { + return NULL; + } + + + address = sdssplitlen(addrs, strlen(addrs), CLUSTER_ADDRESS_SEPARATOR, + strlen(CLUSTER_ADDRESS_SEPARATOR), &address_count); + if(address == NULL || address_count <= 0) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"servers address is error(correct is like: 127.0.0.1:1234,127.0.0.2:5678)"); + return cc; + } + + for(i = 0; i < address_count; i ++) + { + ret = redisClusterAddNode(cc, address[i]); + if(ret != REDIS_OK) + { + sdsfreesplitres(address, address_count); + return cc; + } + } + + sdsfreesplitres(address, address_count); + + cluster_update_route(cc); + + return cc; +} + +redisClusterContext *redisClusterConnect(const char *addrs, int flags) +{ + redisClusterContext *cc; + + cc = redisClusterContextInit(); + + if(cc == NULL) + { + return NULL; + } + + cc->flags |= REDIS_BLOCK; + if(flags) + { + cc->flags |= flags; + } + + return _redisClusterConnect(cc, addrs); +} + +redisClusterContext *redisClusterConnectWithTimeout( + const char *addrs, const struct timeval tv, int flags) +{ + redisClusterContext *cc; + + cc = redisClusterContextInit(); + + if(cc == NULL) + { + return NULL; + } + + cc->flags |= REDIS_BLOCK; + if(flags) + { + cc->flags |= flags; + } + + if (cc->timeout == NULL) + { + cc->timeout = malloc(sizeof(struct timeval)); + } + + memcpy(cc->timeout, &tv, sizeof(struct timeval)); + + return _redisClusterConnect(cc, addrs); +} + +redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags) { + + redisClusterContext *cc; + + cc = redisClusterContextInit(); + + if(cc == NULL) + { + return NULL; + } + + cc->flags &= ~REDIS_BLOCK; + if(flags) + { + cc->flags |= flags; + } + + return _redisClusterConnect(cc, addrs); +} + +redisContext *ctx_get_by_node(cluster_node *node, + const struct timeval *timeout, int flags) +{ + redisContext *c = NULL; + if(node == NULL) + { + return NULL; + } + + c = node->con; + if(c != NULL) + { + if(c->err) + { + redisReconnect(c); + } + + return c; + } + + if(node->host == NULL || node->port <= 0) + { + return NULL; + } + + if(flags & REDIS_BLOCK) + { + if(timeout) + { + c = redisConnectWithTimeout(node->host, node->port, *timeout); + } + else + { + c = redisConnect(node->host, node->port); + } + } + else + { + c = redisConnectNonBlock(node->host, node->port); + } + + node->con = c; + + return c; +} + +static cluster_node *node_get_by_slot(redisClusterContext *cc, uint32_t slot_num) +{ + struct hiarray *slots; + uint32_t slot_count; + cluster_slot **slot; + uint32_t middle, start, end; + uint8_t stop = 0; + + if(cc == NULL) + { + return NULL; + } + + if(slot_num >= REDIS_CLUSTER_SLOTS) + { + return NULL; + } + + slots = cc->slots; + if(slots == NULL) + { + return NULL; + } + slot_count = hiarray_n(slots); + + start = 0; + end = slot_count - 1; + middle = 0; + + do{ + if(start >= end) + { + stop = 1; + middle = end; + } + else + { + middle = start + (end - start)/2; + } + + ASSERT(middle < slot_count); + + slot = hiarray_get(slots, middle); + if((*slot)->start > slot_num) + { + end = middle - 1; + } + else if((*slot)->end < slot_num) + { + start = middle + 1; + } + else + { + return (*slot)->node; + } + + + }while(!stop); + + printf("slot_num : %d\n", slot_num); + printf("slot_count : %d\n", slot_count); + printf("start : %d\n", start); + printf("end : %d\n", end); + printf("middle : %d\n", middle); + + return NULL; +} + + +static cluster_node *node_get_by_table(redisClusterContext *cc, uint32_t slot_num) +{ + if(cc == NULL) + { + return NULL; + } + + if(slot_num >= REDIS_CLUSTER_SLOTS) + { + return NULL; + } + + return cc->table[slot_num]; + +} + +static cluster_node *node_get_witch_connected(redisClusterContext *cc) +{ + dictIterator *di; + dictEntry *de; + struct cluster_node *node; + redisContext *c = NULL; + redisReply *reply = NULL; + + if(cc == NULL || cc->nodes == NULL) + { + return NULL; + } + + di = dictGetIterator(cc->nodes); + while((de = dictNext(di)) != NULL) + { + node = dictGetEntryVal(de); + if(node == NULL) + { + continue; + } + + c = ctx_get_by_node(node, cc->timeout, REDIS_BLOCK); + if(c == NULL || c->err) + { + continue; + } + + reply = redisCommand(c, REDIS_COMMAND_PING); + if(reply != NULL && reply->type == REDIS_REPLY_STATUS && + reply->str != NULL && strcmp(reply->str, "PONG") == 0) + { + freeReplyObject(reply); + reply = NULL; + + dictReleaseIterator(di); + + return node; + } + else if(reply != NULL) + { + freeReplyObject(reply); + reply = NULL; + } + } + + dictReleaseIterator(di); + + return NULL; +} + +static int slot_get_by_command(redisClusterContext *cc, char *cmd, int len) +{ + struct cmd *command = NULL; + struct keypos *kp; + int key_count; + uint32_t i; + int slot_num = -1; + + if(cc == NULL || cmd == NULL || len <= 0) + { + goto done; + } + + command = command_get(); + if(command == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto done; + } + + command->cmd = cmd; + command->clen = len; + redis_parse_cmd(command); + if(command->result != CMD_PARSE_OK) + { + __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "parse command error"); + goto done; + } + + key_count = hiarray_n(command->keys); + + if(key_count <= 0) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "no keys in command(must have keys for redis cluster mode)"); + goto done; + } + else if(key_count == 1) + { + kp = hiarray_get(command->keys, 0); + slot_num = keyHashSlot(kp->start, kp->end - kp->start); + + goto done; + } + + for(i = 0; i < hiarray_n(command->keys); i ++) + { + kp = hiarray_get(command->keys, i); + + slot_num = keyHashSlot(kp->start, kp->end - kp->start); + } + +done: + + if(command != NULL) + { + command->cmd = NULL; + command_destroy(command); + } + + return slot_num; +} + +/* Get the cluster config from one node. + * Return value: config_value string must free by usr. + */ +static char * cluster_config_get(redisClusterContext *cc, + const char *config_name, int *config_value_len) +{ + redisContext *c; + cluster_node *node; + redisReply *reply = NULL, *sub_reply; + char *config_value = NULL; + + if(cc == NULL || config_name == NULL + || config_value_len == NULL) + { + return NULL; + } + + node = node_get_witch_connected(cc); + if(node == NULL) + { + __redisClusterSetError(cc, + REDIS_ERR_OTHER, "no reachable node in cluster"); + goto error; + } + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + + reply = redisCommand(c, "config get %s", config_name); + if(reply == NULL) + { + __redisClusterSetError(cc, + REDIS_ERR_OTHER, "reply for config get is null"); + goto error; + } + + if(reply->type != REDIS_REPLY_ARRAY) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply for config get type is not array"); + goto error; + } + + if(reply->elements != 2) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply for config get elements number is not 2"); + goto error; + } + + sub_reply = reply->element[0]; + if(sub_reply == NULL || sub_reply->type != REDIS_REPLY_STRING) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply for config get config name is not string"); + goto error; + } + + if(strcmp(sub_reply->str, config_name)) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply for config get config name is not we want"); + goto error; + } + + sub_reply = reply->element[1]; + if(sub_reply == NULL || sub_reply->type != REDIS_REPLY_STRING) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply for config get config value type is not string"); + goto error; + } + + config_value = sub_reply->str; + *config_value_len = sub_reply->len; + sub_reply->str= NULL; + + if(reply != NULL) + { + freeReplyObject(reply); + } + + return config_value; + +error: + + if(reply != NULL) + { + freeReplyObject(reply); + } + + return NULL; +} + +/* Helper function for the redisClusterAppendCommand* family of functions. + * + * Write a formatted command to the output buffer. When this family + * is used, you need to call redisGetReply yourself to retrieve + * the reply (or replies in pub/sub). + */ +static int __redisClusterAppendCommand(redisClusterContext *cc, + struct cmd *command) { + + cluster_node *node; + redisContext *c = NULL; + + if(cc == NULL || command == NULL) + { + return REDIS_ERR; + } + + node = node_get_by_table(cc, (uint32_t)command->slot_num); + if(node == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by slot error"); + return REDIS_ERR; + } + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + if(c == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null"); + return REDIS_ERR; + } + else if(c->err) + { + __redisClusterSetError(cc, c->err, c->errstr); + return REDIS_ERR; + } + + if (__redisAppendCommand(c, command->cmd, command->clen) != REDIS_OK) + { + __redisClusterSetError(cc, c->err, c->errstr); + return REDIS_ERR; + } + + return REDIS_OK; +} + +/* Helper function for the redisClusterGetReply* family of functions. + */ +static int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply) +{ + cluster_node *node; + redisContext *c; + + if(cc == NULL || slot_num < 0 || reply == NULL) + { + return REDIS_ERR; + } + + node = node_get_by_table(cc, (uint32_t)slot_num); + if(node == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table is null"); + return REDIS_ERR; + } + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + if(c == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + else if(c->err) + { + if(cc->need_update_route == 0) + { + cc->retry_count ++; + if(cc->retry_count > cc->max_redirect_count) + { + cc->need_update_route = 1; + cc->retry_count = 0; + } + } + __redisClusterSetError(cc, c->err, c->errstr); + return REDIS_ERR; + } + + if(redisGetReply(c, reply) != REDIS_OK) + { + __redisClusterSetError(cc, c->err, c->errstr); + return REDIS_ERR; + } + + if(cluster_reply_error_type(*reply) == CLUSTER_ERR_MOVED) + { + cc->need_update_route = 1; + } + + return REDIS_OK; +} + +static cluster_node *node_get_by_ask_error_reply( + redisClusterContext *cc, redisReply *reply) +{ + sds *part = NULL, *ip_port = NULL; + int part_len = 0, ip_port_len; + dictEntry *de; + cluster_node *node = NULL; + + if(cc == NULL || reply == NULL) + { + return NULL; + } + + if(cluster_reply_error_type(reply) != CLUSTER_ERR_ASK) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply is not ask error!"); + return NULL; + } + + part = sdssplitlen(reply->str, reply->len, " ", 1, &part_len); + + if(part != NULL && part_len == 3) + { + ip_port = sdssplitlen(part[2], sdslen(part[2]), + ":", 1, &ip_port_len); + + if(ip_port != NULL && ip_port_len == 2) + { + de = dictFind(cc->nodes, part[2]); + if(de == NULL) + { + node = hi_alloc(sizeof(cluster_node)); + if(node == NULL) + { + __redisClusterSetError(cc, + REDIS_ERR_OOM, "Out of memory"); + + goto done; + } + + cluster_node_init(node); + node->addr = part[1]; + node->host = ip_port[0]; + node->port = hi_atoi(ip_port[1], sdslen(ip_port[1])); + node->role = REDIS_ROLE_MASTER; + + dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); + + part = NULL; + ip_port = NULL; + } + else + { + node = de->val; + + goto done; + } + } + else + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "ask error reply address part parse error!"); + + goto done; + } + + } + else + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "ask error reply parse error!"); + + goto done; + } + +done: + + if(part != NULL) + { + sdsfreesplitres(part, part_len); + part = NULL; + } + + if(ip_port != NULL) + { + sdsfreesplitres(ip_port, ip_port_len); + ip_port = NULL; + } + + return node; +} + +static void *redis_cluster_command_execute(redisClusterContext *cc, + struct cmd *command) +{ + int ret; + void *reply = NULL; + cluster_node *node; + redisContext *c = NULL; + int error_type; + +retry: + + node = node_get_by_table(cc, (uint32_t)command->slot_num); + if(node == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table error"); + return NULL; + } + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + if(c == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null"); + return NULL; + } + else if(c->err) + { + node = node_get_witch_connected(cc); + if(node == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "no reachable node in cluster"); + return NULL; + } + + cc->retry_count ++; + if(cc->retry_count > cc->max_redirect_count) + { + __redisClusterSetError(cc, REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, + "too many cluster redirect"); + return NULL; + } + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + if(c == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error"); + return NULL; + } + else if(c->err) + { + __redisClusterSetError(cc, c->err, c->errstr); + return NULL; + } + } + +ask_retry: + + if (__redisAppendCommand(c,command->cmd, command->clen) != REDIS_OK) + { + __redisClusterSetError(cc, c->err, c->errstr); + return NULL; + } + + reply = __redisBlockForReply(c); + if(reply == NULL) + { + __redisClusterSetError(cc, c->err, c->errstr); + return NULL; + } + + error_type = cluster_reply_error_type(reply); + if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL) + { + cc->retry_count ++; + if(cc->retry_count > cc->max_redirect_count) + { + __redisClusterSetError(cc, REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, + "too many cluster redirect"); + freeReplyObject(reply); + return NULL; + } + + switch(error_type) + { + case CLUSTER_ERR_MOVED: + freeReplyObject(reply); + reply = NULL; + ret = cluster_update_route(cc); + if(ret != REDIS_OK) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "route update error, please recreate redisClusterContext!"); + return NULL; + } + + goto retry; + + break; + case CLUSTER_ERR_ASK: + node = node_get_by_ask_error_reply(cc, reply); + if(node == NULL) + { + freeReplyObject(reply); + return NULL; + } + + freeReplyObject(reply); + reply = NULL; + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + if(c == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error"); + return NULL; + } + else if(c->err) + { + __redisClusterSetError(cc, c->err, c->errstr); + return NULL; + } + + reply = redisCommand(c, REDIS_COMMAND_ASKING); + if(reply == NULL) + { + __redisClusterSetError(cc, c->err, c->errstr); + return NULL; + } + + freeReplyObject(reply); + reply = NULL; + + goto ask_retry; + + break; + case CLUSTER_ERR_TRYAGAIN: + case CLUSTER_ERR_CROSSSLOT: + case CLUSTER_ERR_CLUSTERDOWN: + freeReplyObject(reply); + reply = NULL; + goto retry; + + break; + default: + + break; + } + } + + return reply; +} + +static int command_pre_fragment(redisClusterContext *cc, + struct cmd *command, hilist *commands) +{ + + struct keypos *kp, *sub_kp; + uint32_t key_count; + uint32_t i, j; + uint32_t idx; + uint32_t key_len; + int slot_num = -1; + struct cmd *sub_command; + struct cmd **sub_commands = NULL; + char num_str[12]; + uint8_t num_str_len; + + + if(command == NULL || commands == NULL) + { + goto done; + } + + key_count = hiarray_n(command->keys); + + sub_commands = hi_zalloc(REDIS_CLUSTER_SLOTS * sizeof(*sub_commands)); + if (sub_commands == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto done; + } + + command->frag_seq = hi_alloc(key_count * sizeof(*command->frag_seq)); + if(command->frag_seq == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto done; + } + + + for(i = 0; i < key_count; i ++) + { + kp = hiarray_get(command->keys, i); + + slot_num = keyHashSlot(kp->start, kp->end - kp->start); + + if(slot_num < 0 || slot_num >= REDIS_CLUSTER_SLOTS) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"keyHashSlot return error"); + goto done; + } + + if (sub_commands[slot_num] == NULL) { + sub_commands[slot_num] = command_get(); + if (sub_commands[slot_num] == NULL) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + slot_num = -1; + goto done; + } + } + + command->frag_seq[i] = sub_command = sub_commands[slot_num]; + + sub_command->narg++; + + sub_kp = hiarray_push(sub_command->keys); + if (sub_kp == NULL) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + slot_num = -1; + goto done; + } + + sub_kp->start = kp->start; + sub_kp->end = kp->end; + + key_len = (uint32_t)(kp->end - kp->start); + + sub_command->clen += key_len + uint_len(key_len); + + sub_command->slot_num = slot_num; + + if (command->type == CMD_REQ_REDIS_MSET) { + uint32_t len = 0; + char *p; + + for (p = sub_kp->end + 1; !isdigit(*p); p++){} + + p = sub_kp->end + 1; + while(!isdigit(*p)) + { + p ++; + } + + for (; isdigit(*p); p++) { + len = len * 10 + (uint32_t)(*p - '0'); + } + + len += CRLF_LEN * 2; + len += (p - sub_kp->end); + sub_kp->remain_len = len; + sub_command->clen += len; + } + } + + for (i = 0; i < REDIS_CLUSTER_SLOTS; i++) { /* prepend command header */ + sub_command = sub_commands[i]; + if (sub_command == NULL) { + continue; + } + + idx = 0; + if (command->type == CMD_REQ_REDIS_MGET) { + //"*%d\r\n$4\r\nmget\r\n" + + sub_command->clen += 5*sub_command->narg; + + sub_command->narg ++; + + hi_itoa(num_str, sub_command->narg); + num_str_len = (uint8_t)(strlen(num_str)); + + sub_command->clen += 13 + num_str_len; + + sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd)); + if(sub_command->cmd == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + slot_num = -1; + goto done; + } + + sub_command->cmd[idx++] = '*'; + memcpy(sub_command->cmd + idx, num_str, num_str_len); + idx += num_str_len; + memcpy(sub_command->cmd + idx, "\r\n$4\r\nmget\r\n", 12); + idx += 12; + + for(j = 0; j < hiarray_n(sub_command->keys); j ++) + { + kp = hiarray_get(sub_command->keys, j); + key_len = (uint32_t)(kp->end - kp->start); + hi_itoa(num_str, key_len); + num_str_len = strlen(num_str); + + sub_command->cmd[idx++] = '$'; + memcpy(sub_command->cmd + idx, num_str, num_str_len); + idx += num_str_len; + memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); + idx += CRLF_LEN; + memcpy(sub_command->cmd + idx, kp->start, key_len); + idx += key_len; + memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); + idx += CRLF_LEN; + } + } else if (command->type == CMD_REQ_REDIS_DEL) { + //"*%d\r\n$3\r\ndel\r\n" + + sub_command->clen += 5*sub_command->narg; + + sub_command->narg ++; + + hi_itoa(num_str, sub_command->narg); + num_str_len = (uint8_t)strlen(num_str); + + sub_command->clen += 12 + num_str_len; + + sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd)); + if(sub_command->cmd == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + slot_num = -1; + goto done; + } + + sub_command->cmd[idx++] = '*'; + memcpy(sub_command->cmd + idx, num_str, num_str_len); + idx += num_str_len; + memcpy(sub_command->cmd + idx, "\r\n$3\r\ndel\r\n", 11); + idx += 11; + + for(j = 0; j < hiarray_n(sub_command->keys); j ++) + { + kp = hiarray_get(sub_command->keys, j); + key_len = (uint32_t)(kp->end - kp->start); + hi_itoa(num_str, key_len); + num_str_len = strlen(num_str); + + sub_command->cmd[idx++] = '$'; + memcpy(sub_command->cmd + idx, num_str, num_str_len); + idx += num_str_len; + memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); + idx += CRLF_LEN; + memcpy(sub_command->cmd + idx, kp->start, key_len); + idx += key_len; + memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); + idx += CRLF_LEN; + } + } else if (command->type == CMD_REQ_REDIS_MSET) { + //"*%d\r\n$4\r\nmset\r\n" + + sub_command->clen += 3*sub_command->narg; + + sub_command->narg *= 2; + + sub_command->narg ++; + + hi_itoa(num_str, sub_command->narg); + num_str_len = (uint8_t)strlen(num_str); + + sub_command->clen += 13 + num_str_len; + + sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd)); + if(sub_command->cmd == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + slot_num = -1; + goto done; + } + + sub_command->cmd[idx++] = '*'; + memcpy(sub_command->cmd + idx, num_str, num_str_len); + idx += num_str_len; + memcpy(sub_command->cmd + idx, "\r\n$4\r\nmset\r\n", 12); + idx += 12; + + for(j = 0; j < hiarray_n(sub_command->keys); j ++) + { + kp = hiarray_get(sub_command->keys, j); + key_len = (uint32_t)(kp->end - kp->start); + hi_itoa(num_str, key_len); + num_str_len = strlen(num_str); + + sub_command->cmd[idx++] = '$'; + memcpy(sub_command->cmd + idx, num_str, num_str_len); + idx += num_str_len; + memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); + idx += CRLF_LEN; + memcpy(sub_command->cmd + idx, kp->start, key_len + kp->remain_len); + idx += key_len + kp->remain_len; + + } + } else { + NOT_REACHED(); + } + + //printf("len : %d\n", sub_command->clen); + //print_string_with_length_fix_CRLF(sub_command->cmd, sub_command->clen); + + sub_command->type = command->type; + + listAddNodeTail(commands, sub_command); + } + +done: + + if(sub_commands != NULL) + { + hi_free(sub_commands); + } + + if(slot_num >= 0 && commands != NULL + && listLength(commands) == 1) + { + listNode *list_node = listFirst(commands); + listDelNode(commands, list_node); + if(command->frag_seq) + { + hi_free(command->frag_seq); + command->frag_seq = NULL; + } + + command->slot_num = slot_num; + } + + return slot_num; +} + +static void *command_post_fragment(redisClusterContext *cc, + struct cmd *command, hilist *commands) +{ + struct cmd *sub_command; + listNode *list_node; + listIter *list_iter; + redisReply *reply, *sub_reply; + long long count = 0; + + list_iter = listGetIterator(commands, AL_START_HEAD); + while((list_node = listNext(list_iter)) != NULL) + { + sub_command = list_node->value; + reply = sub_command->reply; + if(reply == NULL) + { + return NULL; + } + else if(reply->type == REDIS_REPLY_ERROR) + { + return reply; + } + + if (command->type == CMD_REQ_REDIS_MGET) { + if(reply->type != REDIS_REPLY_ARRAY) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be array)"); + return NULL; + } + }else if(command->type == CMD_REQ_REDIS_DEL){ + if(reply->type != REDIS_REPLY_INTEGER) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be integer)"); + return NULL; + } + + count += reply->integer; + }else if(command->type == CMD_REQ_REDIS_MSET){ + if(reply->type != REDIS_REPLY_STATUS || + reply->len != 2 || strcmp(reply->str, REDIS_STATUS_OK) != 0) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be status and ok)"); + return NULL; + } + }else { + NOT_REACHED(); + } + } + + reply = hi_calloc(1,sizeof(*reply)); + + if (reply == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return NULL; + } + + if (command->type == CMD_REQ_REDIS_MGET) { + int i; + uint32_t key_count; + + reply->type = REDIS_REPLY_ARRAY; + + key_count = hiarray_n(command->keys); + + reply->elements = key_count; + reply->element = hi_calloc(key_count, sizeof(*reply)); + if (reply->element == NULL) { + freeReplyObject(reply); + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return NULL; + } + + for (i = key_count - 1; i >= 0; i--) { /* for each key */ + sub_reply = command->frag_seq[i]->reply; /* get it's reply */ + if (sub_reply == NULL) { + freeReplyObject(reply); + __redisClusterSetError(cc,REDIS_ERR_OTHER,"sub reply is null"); + return NULL; + } + + if(sub_reply->type == REDIS_REPLY_STRING) + { + reply->element[i] = sub_reply; + } + else if(sub_reply->type == REDIS_REPLY_ARRAY) + { + if(sub_reply->elements == 0) + { + freeReplyObject(reply); + __redisClusterSetError(cc,REDIS_ERR_OTHER,"sub reply elements error"); + return NULL; + } + + reply->element[i] = sub_reply->element[sub_reply->elements - 1]; + sub_reply->elements --; + } + } + }else if(command->type == CMD_REQ_REDIS_DEL){ + reply->type = REDIS_REPLY_INTEGER; + reply->integer = count; + }else if(command->type == CMD_REQ_REDIS_MSET){ + reply->type = REDIS_REPLY_STATUS; + uint32_t str_len = strlen(REDIS_STATUS_OK); + reply->str = hi_alloc((str_len + 1) * sizeof(char*)); + if(reply->str == NULL) + { + freeReplyObject(reply); + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return NULL; + } + + reply->len = str_len; + memcpy(reply->str, REDIS_STATUS_OK, str_len); + reply->str[str_len] = '\0'; + }else { + NOT_REACHED(); + } + + return reply; +} + +/* + * Split the command into subcommands by slot + * + * Returns slot_num + * If slot_num < 0 or slot_num >= REDIS_CLUSTER_SLOTS means this function runs error; + * Otherwise if the commands > 1 , slot_num is the last subcommand slot number. + */ +static int command_format_by_slot(redisClusterContext *cc, + struct cmd *command, hilist *commands) +{ + struct keypos *kp; + int key_count; + int slot_num = -1; + + if(cc == NULL || commands == NULL || + command == NULL || + command->cmd == NULL || command->clen <= 0) + { + goto done; + } + + + redis_parse_cmd(command); + if(command->result == CMD_PARSE_ENOMEM) + { + __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "Parse command error: out of memory"); + goto done; + } + else if(command->result != CMD_PARSE_OK) + { + __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, command->errstr); + goto done; + } + + key_count = hiarray_n(command->keys); + + if(key_count <= 0) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "No keys in command(must have keys for redis cluster mode)"); + goto done; + } + else if(key_count == 1) + { + kp = hiarray_get(command->keys, 0); + slot_num = keyHashSlot(kp->start, kp->end - kp->start); + command->slot_num = slot_num; + + goto done; + } + + slot_num = command_pre_fragment(cc, command, commands); + +done: + + return slot_num; +} + + +void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count) +{ + if(cc == NULL || max_redirect_count <= 0) + { + return; + } + + cc->max_redirect_count = max_redirect_count; +} + +void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len) { + redisReply *reply = NULL; + int slot_num; + struct cmd *command = NULL, *sub_command; + hilist *commands = NULL; + listNode *list_node; + listIter *list_iter = NULL; + + if(cc == NULL) + { + return NULL; + } + + if(cc->err) + { + cc->err = 0; + memset(cc->errstr, '\0', strlen(cc->errstr)); + } + + command = command_get(); + if(command == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return NULL; + } + + command->cmd = cmd; + command->clen = len; + + commands = listCreate(); + if(commands == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + commands->free = listCommandFree; + + slot_num = command_format_by_slot(cc, command, commands); + + if(slot_num < 0) + { + goto error; + } + else if(slot_num >= REDIS_CLUSTER_SLOTS) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range"); + goto error; + } + + //all keys belong to one slot + if(listLength(commands) == 0) + { + reply = redis_cluster_command_execute(cc, command); + goto done; + } + + ASSERT(listLength(commands) != 1); + + list_iter = listGetIterator(commands, AL_START_HEAD); + while((list_node = listNext(list_iter)) != NULL) + { + sub_command = list_node->value; + + reply = redis_cluster_command_execute(cc, sub_command); + if(reply == NULL) + { + goto error; + } + else if(reply->type == REDIS_REPLY_ERROR) + { + goto done; + } + + sub_command->reply = reply; + } + + reply = command_post_fragment(cc, command, commands); + +done: + + command->cmd = NULL; + command_destroy(command); + + if(commands != NULL) + { + listRelease(commands); + } + + if(list_iter != NULL) + { + listReleaseIterator(list_iter); + } + + cc->retry_count = 0; + + return reply; + +error: + + if(command != NULL) + { + command->cmd = NULL; + command_destroy(command); + } + + if(commands != NULL) + { + listRelease(commands); + } + + if(list_iter != NULL) + { + listReleaseIterator(list_iter); + } + + cc->retry_count = 0; + + return NULL; +} + +void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap) { + redisReply *reply; + char *cmd; + int len; + + if(cc == NULL) + { + return NULL; + } + + len = redisvFormatCommand(&cmd,format,ap); + + if (len == -1) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return NULL; + } else if (len == -2) { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); + return NULL; + } + + reply = redisClusterFormattedCommand(cc, cmd, len); + + free(cmd); + + return reply; +} + +void *redisClusterCommand(redisClusterContext *cc, const char *format, ...) { + va_list ap; + redisReply *reply = NULL; + + va_start(ap,format); + reply = redisClustervCommand(cc, format, ap); + va_end(ap); + + return reply; +} + +void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen) { + redisReply *reply = NULL; + char *cmd; + int len; + + len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); + if (len == -1) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return NULL; + } + + reply = redisClusterFormattedCommand(cc, cmd, len); + + free(cmd); + + return reply; +} + +int redisClusterAppendFormattedCommand(redisClusterContext *cc, + char *cmd, int len) { + int slot_num; + struct cmd *command = NULL, *sub_command; + hilist *commands = NULL; + listNode *list_node; + listIter *list_iter = NULL; + + if(cc->requests == NULL) + { + cc->requests = listCreate(); + if(cc->requests == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + cc->requests->free = listCommandFree; + } + + command = command_get(); + if(command == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + command->cmd = cmd; + command->clen = len; + + commands = listCreate(); + if(commands == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + commands->free = listCommandFree; + + slot_num = command_format_by_slot(cc, command, commands); + + if(slot_num < 0) + { + goto error; + } + else if(slot_num >= REDIS_CLUSTER_SLOTS) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range"); + goto error; + } + + //all keys belong to one slot + if(listLength(commands) == 0) + { + if(__redisClusterAppendCommand(cc, command) == REDIS_OK) + { + goto done; + } + else + { + goto error; + } + } + + ASSERT(listLength(commands) != 1); + + list_iter = listGetIterator(commands, AL_START_HEAD); + while((list_node = listNext(list_iter)) != NULL) + { + sub_command = list_node->value; + + if(__redisClusterAppendCommand(cc, sub_command) == REDIS_OK) + { + continue; + } + else + { + goto error; + } + } + +done: + + if(command->cmd != NULL) + { + command->cmd = NULL; + } + else + { + goto error; + } + + if(commands != NULL) + { + if(listLength(commands) > 0) + { + command->sub_commands = commands; + } + else + { + listRelease(commands); + } + } + + if(list_iter != NULL) + { + listReleaseIterator(list_iter); + } + + listAddNodeTail(cc->requests, command); + + return REDIS_OK; + +error: + + if(command != NULL) + { + command->cmd = NULL; + command_destroy(command); + } + + if(commands != NULL) + { + listRelease(commands); + } + + if(list_iter != NULL) + { + listReleaseIterator(list_iter); + } + + /* Attention: mybe here we must pop the + sub_commands that had append to the nodes. + But now we do not handle it. */ + + return REDIS_ERR; +} + + +int redisClustervAppendCommand(redisClusterContext *cc, + const char *format, va_list ap) { + int ret; + char *cmd; + int len; + + len = redisvFormatCommand(&cmd,format,ap); + if (len == -1) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } else if (len == -2) { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); + return REDIS_ERR; + } + + ret = redisClusterAppendFormattedCommand(cc, cmd, len); + + free(cmd); + + return ret; +} + +int redisClusterAppendCommand(redisClusterContext *cc, + const char *format, ...) { + + int ret; + va_list ap; + + if(cc == NULL || format == NULL) + { + return REDIS_ERR; + } + + va_start(ap,format); + ret = redisClustervAppendCommand(cc, format, ap); + va_end(ap); + + return ret; +} + +int redisClusterAppendCommandArgv(redisClusterContext *cc, + int argc, const char **argv, const size_t *argvlen) { + int ret; + char *cmd; + int len; + + len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); + if (len == -1) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + ret = redisClusterAppendFormattedCommand(cc, cmd, len); + + free(cmd); + + return ret; +} + +static int redisCLusterSendAll(redisClusterContext *cc) +{ + dictIterator *di; + dictEntry *de; + struct cluster_node *node; + redisContext *c = NULL; + int wdone = 0; + + if(cc == NULL || cc->nodes == NULL) + { + return REDIS_ERR; + } + + di = dictGetIterator(cc->nodes); + while((de = dictNext(di)) != NULL) + { + node = dictGetEntryVal(de); + if(node == NULL) + { + continue; + } + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + if(c == NULL) + { + continue; + } + + if (c->flags & REDIS_BLOCK) { + /* Write until done */ + do { + if (redisBufferWrite(c,&wdone) == REDIS_ERR) + { + dictReleaseIterator(di); + return REDIS_ERR; + } + } while (!wdone); + } + } + + dictReleaseIterator(di); + + return REDIS_OK; +} + +static int redisCLusterClearAll(redisClusterContext *cc) +{ + dictIterator *di; + dictEntry *de; + struct cluster_node *node; + redisContext *c = NULL; + + if (cc == NULL) { + return REDIS_ERR; + } + + if (cc->err) { + cc->err = 0; + memset(cc->errstr, '\0', strlen(cc->errstr)); + } + + if (cc->nodes == NULL) { + return REDIS_ERR; + } + di = dictGetIterator(cc->nodes); + while((de = dictNext(di)) != NULL) + { + node = dictGetEntryVal(de); + if(node == NULL) + { + continue; + } + + c = node->con; + if(c == NULL) + { + continue; + } + + redisFree(c); + node->con = NULL; + } + + dictReleaseIterator(di); + + return REDIS_OK; +} + +int redisClusterGetReply(redisClusterContext *cc, void **reply) { + + struct cmd *command, *sub_command; + hilist *commands = NULL; + listNode *list_command, *list_sub_command; + listIter *list_iter; + int slot_num; + void *sub_reply; + + if(cc == NULL || reply == NULL) + return REDIS_ERR; + + cc->err = 0; + cc->errstr[0] = '\0'; + + *reply = NULL; + + if (cc->requests == NULL) + return REDIS_ERR; + + list_command = listFirst(cc->requests); + + //no more reply + if(list_command == NULL) + { + *reply = NULL; + return REDIS_OK; + } + + command = list_command->value; + if(command == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "command in the requests list is null"); + goto error; + } + + slot_num = command->slot_num; + if(slot_num >= 0) + { + listDelNode(cc->requests, list_command); + return __redisClusterGetReply(cc, slot_num, reply); + } + + commands = command->sub_commands; + if(commands == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "sub_commands in command is null"); + goto error; + } + + ASSERT(listLength(commands) != 1); + + list_iter = listGetIterator(commands, AL_START_HEAD); + while((list_sub_command = listNext(list_iter)) != NULL) + { + sub_command = list_sub_command->value; + if(sub_command == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "sub_command is null"); + goto error; + } + + slot_num = sub_command->slot_num; + if(slot_num < 0) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "sub_command slot_num is less then zero"); + goto error; + } + + if(__redisClusterGetReply(cc, slot_num, &sub_reply) != REDIS_OK) + { + goto error; + } + + sub_command->reply = sub_reply; + } + + *reply = command_post_fragment(cc, command, commands); + if(*reply == NULL) + { + goto error; + } + + listDelNode(cc->requests, list_command); + return REDIS_OK; + +error: + + listDelNode(cc->requests, list_command); + return REDIS_ERR; +} + +void redisClusterReset(redisClusterContext *cc) +{ + int status; + void *reply; + + if(cc == NULL || cc->nodes == NULL) + { + return; + } + + if (cc->err) { + redisCLusterClearAll(cc); + } else { + redisCLusterSendAll(cc); + + do { + status = redisClusterGetReply(cc, &reply); + if (status == REDIS_OK) { + freeReplyObject(reply); + } else { + redisCLusterClearAll(cc); + break; + } + } while(reply != NULL); + } + + if(cc->requests) + { + listRelease(cc->requests); + cc->requests = NULL; + } + + if(cc->need_update_route) + { + status = cluster_update_route(cc); + if(status != REDIS_OK) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "route update error, please recreate redisClusterContext!"); + return; + } + cc->need_update_route = 0; + } +} + +/*############redis cluster async############*/ + +/* We want the error field to be accessible directly instead of requiring + * an indirection to the redisContext struct. */ +static void __redisClusterAsyncCopyError(redisClusterAsyncContext *acc) { + if (!acc) + return; + + redisClusterContext *cc = acc->cc; + acc->err = cc->err; + memcpy(acc->errstr, cc->errstr, 128); +} + +static void __redisClusterAsyncSetError(redisClusterAsyncContext *acc, + int type, const char *str) { + + size_t len; + + acc->err = type; + if (str != NULL) { + len = strlen(str); + len = len < (sizeof(acc->errstr)-1) ? len : (sizeof(acc->errstr)-1); + memcpy(acc->errstr,str,len); + acc->errstr[len] = '\0'; + } else { + /* Only REDIS_ERR_IO may lack a description! */ + assert(type == REDIS_ERR_IO); + __redis_strerror_r(errno, acc->errstr, sizeof(acc->errstr)); + } +} + +static redisClusterAsyncContext *redisClusterAsyncInitialize(redisClusterContext *cc) { + redisClusterAsyncContext *acc; + + if(cc == NULL) + { + return NULL; + } + + acc = hi_alloc(sizeof(redisClusterAsyncContext)); + if (acc == NULL) + return NULL; + + acc->cc = cc; + + acc->err = 0; + acc->data = NULL; + acc->adapter = NULL; + acc->attach_fn = NULL; + + acc->onConnect = NULL; + acc->onDisconnect = NULL; + + return acc; +} + +static cluster_async_data *cluster_async_data_get(void) +{ + cluster_async_data *cad; + + cad = hi_alloc(sizeof(cluster_async_data)); + if(cad == NULL) + { + return NULL; + } + + cad->acc = NULL; + cad->command = NULL; + cad->callback = NULL; + cad->privdata = NULL; + cad->retry_count = 0; + + return cad; +} + +static void cluster_async_data_free(cluster_async_data *cad) +{ + if(cad == NULL) + { + return; + } + + if(cad->command != NULL) + { + command_destroy(cad->command); + } + + hi_free(cad); + cad = NULL; +} + +static void unlinkAsyncContextAndNode(redisAsyncContext* ac) +{ + cluster_node *node; + + if (ac->data) { + node = (cluster_node *)(ac->data); + node->acon = NULL; + } +} + +redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, + cluster_node *node) +{ + redisAsyncContext *ac; + + if(node == NULL) + { + return NULL; + } + + ac = node->acon; + if(ac != NULL) + { + if (ac->c.err == 0) { + return ac; + } else { + NOT_REACHED(); + } + } + + if(node->host == NULL || node->port <= 0) + { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error"); + return NULL; + } + + ac = redisAsyncConnect(node->host, node->port); + if(ac == NULL) + { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error"); + return NULL; + } + + if(acc->adapter) + { + acc->attach_fn(ac, acc->adapter); + } + + if(acc->onConnect) + { + redisAsyncSetConnectCallback(ac, acc->onConnect); + } + + if(acc->onDisconnect) + { + redisAsyncSetDisconnectCallback(ac, acc->onDisconnect); + } + + ac->data = node; + ac->dataHandler = unlinkAsyncContextAndNode; + node->acon = ac; + + return ac; +} + +static redisAsyncContext *actx_get_after_update_route_by_slot( + redisClusterAsyncContext *acc, int slot_num) +{ + int ret; + redisClusterContext *cc; + redisAsyncContext *ac; + cluster_node *node; + + if(acc == NULL || slot_num < 0) + { + return NULL; + } + + cc = acc->cc; + if(cc == NULL) + { + return NULL; + } + + ret = cluster_update_route(cc); + if(ret != REDIS_OK) + { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, + "route update error, please recreate redisClusterContext!"); + return NULL; + } + + node = node_get_by_table(cc, (uint32_t)slot_num); + if(node == NULL) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, "node get by table error"); + return NULL; + } + + ac = actx_get_by_node(acc, node); + if(ac == NULL) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, "actx get by node error"); + return NULL; + } + else if(ac->err) + { + __redisClusterAsyncSetError(acc, ac->err, ac->errstr); + return NULL; + } + + return ac; +} + +redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags) { + + redisClusterContext *cc; + redisClusterAsyncContext *acc; + + cc = redisClusterConnectNonBlock(addrs, flags); + if(cc == NULL) + { + return NULL; + } + + acc = redisClusterAsyncInitialize(cc); + if (acc == NULL) { + redisClusterFree(cc); + return NULL; + } + + __redisClusterAsyncCopyError(acc); + + return acc; +} + + +int redisClusterAsyncSetConnectCallback( + redisClusterAsyncContext *acc, redisConnectCallback *fn) +{ + if (acc->onConnect == NULL) { + acc->onConnect = fn; + return REDIS_OK; + } + return REDIS_ERR; +} + +int redisClusterAsyncSetDisconnectCallback( + redisClusterAsyncContext *acc, redisDisconnectCallback *fn) +{ + if (acc->onDisconnect == NULL) { + acc->onDisconnect = fn; + return REDIS_OK; + } + return REDIS_ERR; +} + +static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *privdata) { + int ret; + redisReply *reply = r; + cluster_async_data *cad = privdata; + redisClusterAsyncContext *acc; + redisClusterContext *cc; + redisAsyncContext *ac_retry = NULL; + int error_type; + cluster_node *node; + struct cmd *command; + int64_t now, next; + + if(cad == NULL) + { + goto error; + } + + acc = cad->acc; + if(acc == NULL) + { + goto error; + } + + cc = acc->cc; + if(cc == NULL) + { + goto error; + } + + command = cad->command; + if(command == NULL) + { + goto error; + } + + if(reply == NULL) + { + //Note: + //I can't decide witch is the best way to deal with connect + //problem for hiredis cluster async api. + //But now the way is : when enough null reply for a node, + //we will update the route after the cluster node timeout. + //If you have a better idea, please contact with me. Thank you. + //My email: diguo58@gmail.com + + node = (cluster_node *)(ac->data); + ASSERT(node != NULL); + + __redisClusterAsyncSetError(acc, + ac->err, ac->errstr); + + if(cc->update_route_time != 0) + { + now = hi_usec_now(); + if(now >= cc->update_route_time) + { + ret = cluster_update_route(cc); + if(ret != REDIS_OK) + { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, + "route update error, please recreate redisClusterContext!"); + } + + cc->update_route_time = 0LL; + } + + goto done; + } + + node->failure_count ++; + if(node->failure_count > cc->max_redirect_count) + { + char *cluster_timeout_str; + int cluster_timeout_str_len; + int cluster_timeout; + + node->failure_count = 0; + if(cc->update_route_time != 0) + { + goto done; + } + + cluster_timeout_str = cluster_config_get(cc, + "cluster-node-timeout", &cluster_timeout_str_len); + if(cluster_timeout_str == NULL) + { + __redisClusterAsyncSetError(acc, + cc->err, cc->errstr); + goto done; + } + + cluster_timeout = hi_atoi(cluster_timeout_str, + cluster_timeout_str_len); + free(cluster_timeout_str); + if(cluster_timeout <= 0) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, + "cluster_timeout_str convert to integer error"); + goto done; + } + + now = hi_usec_now(); + if (now < 0) { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, + "get now usec time error"); + goto done; + } + + next = now + (cluster_timeout * 1000LL); + + cc->update_route_time = next; + + } + + goto done; + } + + error_type = cluster_reply_error_type(reply); + + if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL) + { + cad->retry_count ++; + if(cad->retry_count > cc->max_redirect_count) + { + cad->retry_count = 0; + __redisClusterAsyncSetError(acc, + REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, + "too many cluster redirect"); + goto done; + } + + switch(error_type) + { + case CLUSTER_ERR_MOVED: + ac_retry = actx_get_after_update_route_by_slot(acc, command->slot_num); + if(ac_retry == NULL) + { + goto done; + } + + break; + case CLUSTER_ERR_ASK: + node = node_get_by_ask_error_reply(cc, reply); + if(node == NULL) + { + __redisClusterAsyncSetError(acc, + cc->err, cc->errstr); + goto done; + } + + ac_retry = actx_get_by_node(acc, node); + if(ac_retry == NULL) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, "actx get by node error"); + goto done; + } + else if(ac_retry->err) + { + __redisClusterAsyncSetError(acc, + ac_retry->err, ac_retry->errstr); + goto done; + } + + ret = redisAsyncCommand(ac_retry, + NULL,NULL,REDIS_COMMAND_ASKING); + if(ret != REDIS_OK) + { + goto error; + } + + break; + case CLUSTER_ERR_TRYAGAIN: + case CLUSTER_ERR_CROSSSLOT: + case CLUSTER_ERR_CLUSTERDOWN: + ac_retry = ac; + + break; + default: + + goto done; + break; + } + + goto retry; + } + +done: + + if(acc->err) + { + cad->callback(acc, NULL, cad->privdata); + } + else + { + cad->callback(acc, r, cad->privdata); + } + + if(cc->err) + { + cc->err = 0; + memset(cc->errstr, '\0', strlen(cc->errstr)); + } + + if(acc->err) + { + acc->err = 0; + memset(acc->errstr, '\0', strlen(acc->errstr)); + } + + if(cad != NULL) + { + cluster_async_data_free(cad); + } + + return; + +retry: + + ret = redisAsyncFormattedCommand(ac_retry, + redisClusterAsyncCallback,cad,command->cmd,command->clen); + if(ret != REDIS_OK) + { + goto error; + } + + return; + +error: + + if(cad != NULL) + { + cluster_async_data_free(cad); + } +} + +int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, void *privdata, char *cmd, int len) { + + redisClusterContext *cc; + int status = REDIS_OK; + int slot_num; + cluster_node *node; + redisAsyncContext *ac; + struct cmd *command = NULL; + hilist *commands = NULL; + cluster_async_data *cad; + + if(acc == NULL) + { + return REDIS_ERR; + } + + cc = acc->cc; + + if(cc->err) + { + cc->err = 0; + memset(cc->errstr, '\0', strlen(cc->errstr)); + } + + if(acc->err) + { + acc->err = 0; + memset(acc->errstr, '\0', strlen(acc->errstr)); + } + + command = command_get(); + if(command == NULL) + { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + command->cmd = malloc(len*sizeof(*command->cmd)); + if(command->cmd == NULL) + { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + memcpy(command->cmd, cmd, len); + command->clen = len; + + commands = listCreate(); + if(commands == NULL) + { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + commands->free = listCommandFree; + + slot_num = command_format_by_slot(cc, command, commands); + + if(slot_num < 0) + { + __redisClusterAsyncSetError(acc, + cc->err, cc->errstr); + goto error; + } + else if(slot_num >= REDIS_CLUSTER_SLOTS) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER,"slot_num is out of range"); + goto error; + } + + //all keys not belong to one slot + if(listLength(commands) > 0) + { + ASSERT(listLength(commands) != 1); + + __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER, + "Asynchronous API now not support multi-key command"); + goto error; + } + + node = node_get_by_table(cc, (uint32_t) slot_num); + if(node == NULL) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, "node get by table error"); + goto error; + } + + ac = actx_get_by_node(acc, node); + if(ac == NULL) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, "actx get by node error"); + goto error; + } + else if(ac->err) + { + __redisClusterAsyncSetError(acc, ac->err, ac->errstr); + goto error; + } + + cad = cluster_async_data_get(); + if(cad == NULL) + { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + cad->acc = acc; + cad->command = command; + cad->callback = fn; + cad->privdata = privdata; + + status = redisAsyncFormattedCommand(ac, + redisClusterAsyncCallback,cad,cmd,len); + if(status != REDIS_OK) + { + goto error; + } + + if(commands != NULL) + { + listRelease(commands); + } + + return REDIS_OK; + +error: + + if(command != NULL) + { + command_destroy(command); + } + + if(commands != NULL) + { + listRelease(commands); + } + + return REDIS_ERR; +} + + +int redisClustervAsyncCommand(redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap) { + int ret; + char *cmd; + int len; + + if(acc == NULL) + { + return REDIS_ERR; + } + + len = redisvFormatCommand(&cmd,format,ap); + if (len == -1) { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } else if (len == -2) { + __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER,"Invalid format string"); + return REDIS_ERR; + } + + ret = redisClusterAsyncFormattedCommand(acc, fn, privdata, cmd, len); + + free(cmd); + + return ret; +} + +int redisClusterAsyncCommand(redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, void *privdata, const char *format, ...) { + int ret; + va_list ap; + + va_start(ap,format); + ret = redisClustervAsyncCommand(acc, fn, privdata, format, ap); + va_end(ap); + + return ret; +} + +int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { + int ret; + char *cmd; + int len; + + len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); + if (len == -1) { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + ret = redisClusterAsyncFormattedCommand(acc, fn, privdata, cmd, len); + + free(cmd); + + return ret; +} + +void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) { + + redisClusterContext *cc; + redisAsyncContext *ac; + dictIterator *di; + dictEntry *de; + dict *nodes; + struct cluster_node *node; + + if(acc == NULL) + { + return; + } + + cc = acc->cc; + + nodes = cc->nodes; + + if(nodes == NULL) + { + return; + } + + di = dictGetIterator(nodes); + + while((de = dictNext(di)) != NULL) + { + node = dictGetEntryVal(de); + + ac = node->acon; + + if(ac == NULL || ac->err) + { + continue; + } + + redisAsyncDisconnect(ac); + + node->acon = NULL; + } +} + +void redisClusterAsyncFree(redisClusterAsyncContext *acc) +{ + redisClusterContext *cc; + + if(acc == NULL) + { + return; + } + + cc = acc->cc; + + redisClusterFree(cc); + + hi_free(acc); +} + diff --git a/ext/hiredis-vip-0.3.0/hircluster.h b/ext/hiredis-vip-0.3.0/hircluster.h new file mode 100644 index 000000000..5b9c5a358 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hircluster.h @@ -0,0 +1,178 @@ + +#ifndef __HIRCLUSTER_H +#define __HIRCLUSTER_H + +#include "hiredis.h" +#include "async.h" + +#define HIREDIS_VIP_MAJOR 0 +#define HIREDIS_VIP_MINOR 3 +#define HIREDIS_VIP_PATCH 0 + +#define REDIS_CLUSTER_SLOTS 16384 + +#define REDIS_ROLE_NULL 0 +#define REDIS_ROLE_MASTER 1 +#define REDIS_ROLE_SLAVE 2 + + +#define HIRCLUSTER_FLAG_NULL 0x0 +/* The flag to decide whether add slave node in + * redisClusterContext->nodes. This is set in the + * least significant bit of the flags field in + * redisClusterContext. (1000000000000) */ +#define HIRCLUSTER_FLAG_ADD_SLAVE 0x1000 +/* The flag to decide whether add open slot + * for master node. (10000000000000) */ +#define HIRCLUSTER_FLAG_ADD_OPENSLOT 0x2000 +/* The flag to decide whether get the route + * table by 'cluster slots' command. Default + * is 'cluster nodes' command.*/ +#define HIRCLUSTER_FLAG_ROUTE_USE_SLOTS 0x4000 + +struct dict; +struct hilist; + +typedef struct cluster_node +{ + sds name; + sds addr; + sds host; + int port; + uint8_t role; + uint8_t myself; /* myself ? */ + redisContext *con; + redisAsyncContext *acon; + struct hilist *slots; + struct hilist *slaves; + int failure_count; + void *data; /* Not used by hiredis */ + struct hiarray *migrating; /* copen_slot[] */ + struct hiarray *importing; /* copen_slot[] */ +}cluster_node; + +typedef struct cluster_slot +{ + uint32_t start; + uint32_t end; + cluster_node *node; /* master that this slot region belong to */ +}cluster_slot; + +typedef struct copen_slot +{ + uint32_t slot_num; /* slot number */ + int migrate; /* migrating or importing? */ + sds remote_name; /* name for the node that this slot migrating to/importing from */ + cluster_node *node; /* master that this slot belong to */ +}copen_slot; + +#ifdef __cplusplus +extern "C" { +#endif + +/* Context for a connection to Redis cluster */ +typedef struct redisClusterContext { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + sds ip; + int port; + + int flags; + + enum redisConnectionType connection_type; + struct timeval *timeout; + + struct hiarray *slots; + + struct dict *nodes; + cluster_node *table[REDIS_CLUSTER_SLOTS]; + + uint64_t route_version; + + int max_redirect_count; + int retry_count; + + struct hilist *requests; + + int need_update_route; + int64_t update_route_time; +} redisClusterContext; + +redisClusterContext *redisClusterConnect(const char *addrs, int flags); +redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, + const struct timeval tv, int flags); +redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); + +void redisClusterFree(redisClusterContext *cc); + +void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); + +void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len); +void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap); +void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); +void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); + +redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); + +int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len); +int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap); +int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); +int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); +int redisClusterGetReply(redisClusterContext *cc, void **reply); +void redisClusterReset(redisClusterContext *cc); + +int cluster_update_route(redisClusterContext *cc); +int test_cluster_update_route(redisClusterContext *cc); +struct dict *parse_cluster_nodes(redisClusterContext *cc, char *str, int str_len, int flags); +struct dict *parse_cluster_slots(redisClusterContext *cc, redisReply *reply, int flags); + + +/*############redis cluster async############*/ + +struct redisClusterAsyncContext; + +typedef int (adapterAttachFn)(redisAsyncContext*, void*); + +typedef void (redisClusterCallbackFn)(struct redisClusterAsyncContext*, void*, void*); + +/* Context for an async connection to Redis */ +typedef struct redisClusterAsyncContext { + + redisClusterContext *cc; + + /* Setup error flags so they can be used directly. */ + int err; + char errstr[128]; /* String representation of error when applicable */ + + /* Not used by hiredis */ + void *data; + + void *adapter; + adapterAttachFn *attach_fn; + + /* Called when either the connection is terminated due to an error or per + * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ + redisDisconnectCallback *onDisconnect; + + /* Called when the first write event was received. */ + redisConnectCallback *onConnect; + +} redisClusterAsyncContext; + +redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); +int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); +int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); +int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len); +int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap); +int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...); +int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); +void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); +void redisClusterAsyncFree(redisClusterAsyncContext *acc); + +redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, cluster_node *node); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-vip-0.3.0/hiredis.c b/ext/hiredis-vip-0.3.0/hiredis.c new file mode 100644 index 000000000..73d0251bc --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hiredis.c @@ -0,0 +1,1021 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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 "fmacros.h" +#include +#include +#include +#include +#include +#include + +#include "hiredis.h" +#include "net.h" +#include "sds.h" + +static redisReply *createReplyObject(int type); +static void *createStringObject(const redisReadTask *task, char *str, size_t len); +static void *createArrayObject(const redisReadTask *task, int elements); +static void *createIntegerObject(const redisReadTask *task, long long value); +static void *createNilObject(const redisReadTask *task); + +/* Default set of functions to build the reply. Keep in mind that such a + * function returning NULL is interpreted as OOM. */ +static redisReplyObjectFunctions defaultFunctions = { + createStringObject, + createArrayObject, + createIntegerObject, + createNilObject, + freeReplyObject +}; + +/* Create a reply object */ +static redisReply *createReplyObject(int type) { + redisReply *r = calloc(1,sizeof(*r)); + + if (r == NULL) + return NULL; + + r->type = type; + return r; +} + +/* Free a reply object */ +void freeReplyObject(void *reply) { + redisReply *r = reply; + size_t j; + + if (r == NULL) + return; + + switch(r->type) { + case REDIS_REPLY_INTEGER: + break; /* Nothing to free */ + case REDIS_REPLY_ARRAY: + if (r->element != NULL) { + for (j = 0; j < r->elements; j++) + if (r->element[j] != NULL) + freeReplyObject(r->element[j]); + free(r->element); + } + break; + case REDIS_REPLY_ERROR: + case REDIS_REPLY_STATUS: + case REDIS_REPLY_STRING: + if (r->str != NULL) + free(r->str); + break; + } + free(r); +} + +static void *createStringObject(const redisReadTask *task, char *str, size_t len) { + redisReply *r, *parent; + char *buf; + + r = createReplyObject(task->type); + if (r == NULL) + return NULL; + + buf = malloc(len+1); + if (buf == NULL) { + freeReplyObject(r); + return NULL; + } + + assert(task->type == REDIS_REPLY_ERROR || + task->type == REDIS_REPLY_STATUS || + task->type == REDIS_REPLY_STRING); + + /* Copy string value */ + memcpy(buf,str,len); + buf[len] = '\0'; + r->str = buf; + r->len = len; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createArrayObject(const redisReadTask *task, int elements) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_ARRAY); + if (r == NULL) + return NULL; + + if (elements > 0) { + r->element = calloc(elements,sizeof(redisReply*)); + if (r->element == NULL) { + freeReplyObject(r); + return NULL; + } + } + + r->elements = elements; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createIntegerObject(const redisReadTask *task, long long value) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_INTEGER); + if (r == NULL) + return NULL; + + r->integer = value; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createNilObject(const redisReadTask *task) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_NIL); + if (r == NULL) + return NULL; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +/* Return the number of digits of 'v' when converted to string in radix 10. + * Implementation borrowed from link in redis/src/util.c:string2ll(). */ +static uint32_t countDigits(uint64_t v) { + uint32_t result = 1; + for (;;) { + if (v < 10) return result; + if (v < 100) return result + 1; + if (v < 1000) return result + 2; + if (v < 10000) return result + 3; + v /= 10000U; + result += 4; + } +} + +/* Helper that calculates the bulk length given a certain string length. */ +static size_t bulklen(size_t len) { + return 1+countDigits(len)+2+len+2; +} + +int redisvFormatCommand(char **target, const char *format, va_list ap) { + const char *c = format; + char *cmd = NULL; /* final command */ + int pos; /* position in final command */ + sds curarg, newarg; /* current argument */ + int touched = 0; /* was the current argument touched? */ + char **curargv = NULL, **newargv = NULL; + int argc = 0; + int totlen = 0; + int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */ + int j; + + /* Abort if there is not target to set */ + if (target == NULL) + return -1; + + /* Build the command string accordingly to protocol */ + curarg = sdsempty(); + if (curarg == NULL) + return -1; + + while(*c != '\0') { + if (*c != '%' || c[1] == '\0') { + if (*c == ' ') { + if (touched) { + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto memory_err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); + + /* curarg is put in argv so it can be overwritten. */ + curarg = sdsempty(); + if (curarg == NULL) goto memory_err; + touched = 0; + } + } else { + newarg = sdscatlen(curarg,c,1); + if (newarg == NULL) goto memory_err; + curarg = newarg; + touched = 1; + } + } else { + char *arg; + size_t size; + + /* Set newarg so it can be checked even if it is not touched. */ + newarg = curarg; + + switch(c[1]) { + case 's': + arg = va_arg(ap,char*); + size = strlen(arg); + if (size > 0) + newarg = sdscatlen(curarg,arg,size); + break; + case 'b': + arg = va_arg(ap,char*); + size = va_arg(ap,size_t); + if (size > 0) + newarg = sdscatlen(curarg,arg,size); + break; + case '%': + newarg = sdscat(curarg,"%"); + break; + default: + /* Try to detect printf format */ + { + static const char intfmts[] = "diouxX"; + static const char flags[] = "#0-+ "; + char _format[16]; + const char *_p = c+1; + size_t _l = 0; + va_list _cpy; + + /* Flags */ + while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; + + /* Field width */ + while (*_p != '\0' && isdigit(*_p)) _p++; + + /* Precision */ + if (*_p == '.') { + _p++; + while (*_p != '\0' && isdigit(*_p)) _p++; + } + + /* Copy va_list before consuming with va_arg */ + va_copy(_cpy,ap); + + /* Integer conversion (without modifiers) */ + if (strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); + goto fmt_valid; + } + + /* Double conversion (without modifiers) */ + if (strchr("eEfFgGaA",*_p) != NULL) { + va_arg(ap,double); + goto fmt_valid; + } + + /* Size: char */ + if (_p[0] == 'h' && _p[1] == 'h') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* char gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: short */ + if (_p[0] == 'h') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* short gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long long */ + if (_p[0] == 'l' && _p[1] == 'l') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long long); + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long */ + if (_p[0] == 'l') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long); + goto fmt_valid; + } + goto fmt_invalid; + } + + fmt_invalid: + va_end(_cpy); + goto format_err; + + fmt_valid: + _l = (_p+1)-c; + if (_l < sizeof(_format)-2) { + memcpy(_format,c,_l); + _format[_l] = '\0'; + newarg = sdscatvprintf(curarg,_format,_cpy); + + /* Update current position (note: outer blocks + * increment c twice so compensate here) */ + c = _p-1; + } + + va_end(_cpy); + break; + } + } + + if (newarg == NULL) goto memory_err; + curarg = newarg; + + touched = 1; + c++; + } + c++; + } + + /* Add the last argument if needed */ + if (touched) { + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto memory_err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); + } else { + sdsfree(curarg); + } + + /* Clear curarg because it was put in curargv or was free'd. */ + curarg = NULL; + + /* Add bytes needed to hold multi bulk count */ + totlen += 1+countDigits(argc)+2; + + /* Build the command at protocol level */ + cmd = malloc(totlen+1); + if (cmd == NULL) goto memory_err; + + pos = sprintf(cmd,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); + memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); + pos += sdslen(curargv[j]); + sdsfree(curargv[j]); + cmd[pos++] = '\r'; + cmd[pos++] = '\n'; + } + assert(pos == totlen); + cmd[pos] = '\0'; + + free(curargv); + *target = cmd; + return totlen; + +format_err: + error_type = -2; + goto cleanup; + +memory_err: + error_type = -1; + goto cleanup; + +cleanup: + if (curargv) { + while(argc--) + sdsfree(curargv[argc]); + free(curargv); + } + + sdsfree(curarg); + + /* No need to check cmd since it is the last statement that can fail, + * but do it anyway to be as defensive as possible. */ + if (cmd != NULL) + free(cmd); + + return error_type; +} + +/* Format a command according to the Redis protocol. This function + * takes a format similar to printf: + * + * %s represents a C null terminated string you want to interpolate + * %b represents a binary safe string + * + * When using %b you need to provide both the pointer to the string + * and the length in bytes as a size_t. Examples: + * + * len = redisFormatCommand(target, "GET %s", mykey); + * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); + */ +int redisFormatCommand(char **target, const char *format, ...) { + va_list ap; + int len; + va_start(ap,format); + len = redisvFormatCommand(target,format,ap); + va_end(ap); + + /* The API says "-1" means bad result, but we now also return "-2" in some + * cases. Force the return value to always be -1. */ + if (len < 0) + len = -1; + + return len; +} + +/* Format a command according to the Redis protocol using an sds string and + * sdscatfmt for the processing of arguments. This function takes the + * number of arguments, an array with arguments and an array with their + * lengths. If the latter is set to NULL, strlen will be used to compute the + * argument lengths. + */ +int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, + const size_t *argvlen) +{ + sds cmd; + unsigned long long totlen; + int j; + size_t len; + + /* Abort on a NULL target */ + if (target == NULL) + return -1; + + /* Calculate our total size */ + totlen = 1+countDigits(argc)+2; + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + totlen += bulklen(len); + } + + /* Use an SDS string for command construction */ + cmd = sdsempty(); + if (cmd == NULL) + return -1; + + /* We already know how much storage we need */ + cmd = sdsMakeRoomFor(cmd, totlen); + if (cmd == NULL) + return -1; + + /* Construct command */ + cmd = sdscatfmt(cmd, "*%i\r\n", argc); + for (j=0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + cmd = sdscatfmt(cmd, "$%T\r\n", len); + cmd = sdscatlen(cmd, argv[j], len); + cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); + } + + assert(sdslen(cmd)==totlen); + + *target = cmd; + return totlen; +} + +void redisFreeSdsCommand(sds cmd) { + sdsfree(cmd); +} + +/* Format a command according to the Redis protocol. This function takes the + * number of arguments, an array with arguments and an array with their + * lengths. If the latter is set to NULL, strlen will be used to compute the + * argument lengths. + */ +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { + char *cmd = NULL; /* final command */ + int pos; /* position in final command */ + size_t len; + int totlen, j; + + /* Abort on a NULL target */ + if (target == NULL) + return -1; + + /* Calculate number of bytes needed for the command */ + totlen = 1+countDigits(argc)+2; + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + totlen += bulklen(len); + } + + /* Build the command at protocol level */ + cmd = malloc(totlen+1); + if (cmd == NULL) + return -1; + + pos = sprintf(cmd,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + pos += sprintf(cmd+pos,"$%zu\r\n",len); + memcpy(cmd+pos,argv[j],len); + pos += len; + cmd[pos++] = '\r'; + cmd[pos++] = '\n'; + } + assert(pos == totlen); + cmd[pos] = '\0'; + + *target = cmd; + return totlen; +} + +void redisFreeCommand(char *cmd) { + free(cmd); +} + +void __redisSetError(redisContext *c, int type, const char *str) { + size_t len; + + c->err = type; + if (str != NULL) { + len = strlen(str); + len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); + memcpy(c->errstr,str,len); + c->errstr[len] = '\0'; + } else { + /* Only REDIS_ERR_IO may lack a description! */ + assert(type == REDIS_ERR_IO); + __redis_strerror_r(errno, c->errstr, sizeof(c->errstr)); + } +} + +redisReader *redisReaderCreate(void) { + return redisReaderCreateWithFunctions(&defaultFunctions); +} + +static redisContext *redisContextInit(void) { + redisContext *c; + + c = calloc(1,sizeof(redisContext)); + if (c == NULL) + return NULL; + + c->err = 0; + c->errstr[0] = '\0'; + c->obuf = sdsempty(); + c->reader = redisReaderCreate(); + c->tcp.host = NULL; + c->tcp.source_addr = NULL; + c->unix_sock.path = NULL; + c->timeout = NULL; + + if (c->obuf == NULL || c->reader == NULL) { + redisFree(c); + return NULL; + } + + return c; +} + +void redisFree(redisContext *c) { + if (c == NULL) + return; + if (c->fd > 0) + close(c->fd); + if (c->obuf != NULL) + sdsfree(c->obuf); + if (c->reader != NULL) + redisReaderFree(c->reader); + if (c->tcp.host) + free(c->tcp.host); + if (c->tcp.source_addr) + free(c->tcp.source_addr); + if (c->unix_sock.path) + free(c->unix_sock.path); + if (c->timeout) + free(c->timeout); + free(c); +} + +int redisFreeKeepFd(redisContext *c) { + int fd = c->fd; + c->fd = -1; + redisFree(c); + return fd; +} + +int redisReconnect(redisContext *c) { + c->err = 0; + memset(c->errstr, '\0', strlen(c->errstr)); + + if (c->fd > 0) { + close(c->fd); + } + + sdsfree(c->obuf); + redisReaderFree(c->reader); + + c->obuf = sdsempty(); + c->reader = redisReaderCreate(); + + if (c->connection_type == REDIS_CONN_TCP) { + return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port, + c->timeout, c->tcp.source_addr); + } else if (c->connection_type == REDIS_CONN_UNIX) { + return redisContextConnectUnix(c, c->unix_sock.path, c->timeout); + } else { + /* Something bad happened here and shouldn't have. There isn't + enough information in the context to reconnect. */ + __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect"); + } + + return REDIS_ERR; +} + +/* Connect to a Redis instance. On error the field error in the returned + * context will be set to the return value of the error function. + * When no set of reply functions is given, the default set will be used. */ +redisContext *redisConnect(const char *ip, int port) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,NULL); + return c; +} + +redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,&tv); + return c; +} + +redisContext *redisConnectNonBlock(const char *ip, int port) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags &= ~REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,NULL); + return c; +} + +redisContext *redisConnectBindNonBlock(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisContextInit(); + c->flags &= ~REDIS_BLOCK; + redisContextConnectBindTcp(c,ip,port,NULL,source_addr); + return c; +} + +redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisContextInit(); + c->flags &= ~REDIS_BLOCK; + c->flags |= REDIS_REUSEADDR; + redisContextConnectBindTcp(c,ip,port,NULL,source_addr); + return c; +} + +redisContext *redisConnectUnix(const char *path) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectUnix(c,path,NULL); + return c; +} + +redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectUnix(c,path,&tv); + return c; +} + +redisContext *redisConnectUnixNonBlock(const char *path) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags &= ~REDIS_BLOCK; + redisContextConnectUnix(c,path,NULL); + return c; +} + +redisContext *redisConnectFd(int fd) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->fd = fd; + c->flags |= REDIS_BLOCK | REDIS_CONNECTED; + return c; +} + +/* Set read/write timeout on a blocking socket. */ +int redisSetTimeout(redisContext *c, const struct timeval tv) { + if (c->flags & REDIS_BLOCK) + return redisContextSetTimeout(c,tv); + return REDIS_ERR; +} + +/* Enable connection KeepAlive. */ +int redisEnableKeepAlive(redisContext *c) { + if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK) + return REDIS_ERR; + return REDIS_OK; +} + +/* Use this function to handle a read event on the descriptor. It will try + * and read some bytes from the socket and feed them to the reply parser. + * + * After this function is called, you may use redisContextReadReply to + * see if there is a reply available. */ +int redisBufferRead(redisContext *c) { + char buf[1024*16]; + int nread; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + + nread = read(c->fd,buf,sizeof(buf)); + if (nread == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + } else if (nread == 0) { + __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); + return REDIS_ERR; + } else { + if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + } + return REDIS_OK; +} + +/* Write the output buffer to the socket. + * + * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was + * succesfully written to the socket. When the buffer is empty after the + * write operation, "done" is set to 1 (if given). + * + * Returns REDIS_ERR if an error occured trying to write and sets + * c->errstr to hold the appropriate error string. + */ +int redisBufferWrite(redisContext *c, int *done) { + int nwritten; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + + if (sdslen(c->obuf) > 0) { + nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); + if (nwritten == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + } else if (nwritten > 0) { + if (nwritten == (signed)sdslen(c->obuf)) { + sdsfree(c->obuf); + c->obuf = sdsempty(); + } else { + sdsrange(c->obuf,nwritten,-1); + } + } + } + if (done != NULL) *done = (sdslen(c->obuf) == 0); + return REDIS_OK; +} + +/* Internal helper function to try and get a reply from the reader, + * or set an error in the context otherwise. */ +int redisGetReplyFromReader(redisContext *c, void **reply) { + if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + return REDIS_OK; +} + +int redisGetReply(redisContext *c, void **reply) { + int wdone = 0; + void *aux = NULL; + + /* Try to read pending replies */ + if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; + + /* For the blocking context, flush output buffer and read reply */ + if (aux == NULL && c->flags & REDIS_BLOCK) { + /* Write until done */ + do { + if (redisBufferWrite(c,&wdone) == REDIS_ERR) + return REDIS_ERR; + } while (!wdone); + + /* Read until there is a reply */ + do { + if (redisBufferRead(c) == REDIS_ERR) + return REDIS_ERR; + if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; + } while (aux == NULL); + } + + /* Set reply object */ + if (reply != NULL) *reply = aux; + return REDIS_OK; +} + + +/* Helper function for the redisAppendCommand* family of functions. + * + * Write a formatted command to the output buffer. When this family + * is used, you need to call redisGetReply yourself to retrieve + * the reply (or replies in pub/sub). + */ +int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { + sds newbuf; + + newbuf = sdscatlen(c->obuf,cmd,len); + if (newbuf == NULL) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + c->obuf = newbuf; + return REDIS_OK; +} + +int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) { + + if (__redisAppendCommand(c, cmd, len) != REDIS_OK) { + return REDIS_ERR; + } + + return REDIS_OK; +} + +int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { + char *cmd; + int len; + + len = redisvFormatCommand(&cmd,format,ap); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } else if (len == -2) { + __redisSetError(c,REDIS_ERR_OTHER,"Invalid format string"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + free(cmd); + return REDIS_ERR; + } + + free(cmd); + return REDIS_OK; +} + +int redisAppendCommand(redisContext *c, const char *format, ...) { + va_list ap; + int ret; + + va_start(ap,format); + ret = redisvAppendCommand(c,format,ap); + va_end(ap); + return ret; +} + +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { + sds cmd; + int len; + + len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + sdsfree(cmd); + return REDIS_ERR; + } + + sdsfree(cmd); + return REDIS_OK; +} + +/* Helper function for the redisCommand* family of functions. + * + * Write a formatted command to the output buffer. If the given context is + * blocking, immediately read the reply into the "reply" pointer. When the + * context is non-blocking, the "reply" pointer will not be used and the + * command is simply appended to the write buffer. + * + * Returns the reply when a reply was succesfully retrieved. Returns NULL + * otherwise. When NULL is returned in a blocking context, the error field + * in the context will be set. + */ +static void *__redisBlockForReply(redisContext *c) { + void *reply; + + if (c->flags & REDIS_BLOCK) { + if (redisGetReply(c,&reply) != REDIS_OK) + return NULL; + return reply; + } + return NULL; +} + +void *redisvCommand(redisContext *c, const char *format, va_list ap) { + if (redisvAppendCommand(c,format,ap) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); +} + +void *redisCommand(redisContext *c, const char *format, ...) { + va_list ap; + void *reply = NULL; + va_start(ap,format); + reply = redisvCommand(c,format,ap); + va_end(ap); + return reply; +} + +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { + if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); +} diff --git a/ext/hiredis-vip-0.3.0/hiredis.h b/ext/hiredis-vip-0.3.0/hiredis.h new file mode 100644 index 000000000..87f7366f8 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hiredis.h @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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. + */ + +#ifndef __HIREDIS_H +#define __HIREDIS_H +#include "read.h" +#include /* for va_list */ +#include /* for struct timeval */ +#include /* uintXX_t, etc */ +#include "sds.h" /* for sds */ + +#define HIREDIS_MAJOR 0 +#define HIREDIS_MINOR 13 +#define HIREDIS_PATCH 1 + +/* Connection type can be blocking or non-blocking and is set in the + * least significant bit of the flags field in redisContext. */ +#define REDIS_BLOCK 0x1 + +/* Connection may be disconnected before being free'd. The second bit + * in the flags field is set when the context is connected. */ +#define REDIS_CONNECTED 0x2 + +/* The async API might try to disconnect cleanly and flush the output + * buffer and read all subsequent replies before disconnecting. + * This flag means no new commands can come in and the connection + * should be terminated once all replies have been read. */ +#define REDIS_DISCONNECTING 0x4 + +/* Flag specific to the async API which means that the context should be clean + * up as soon as possible. */ +#define REDIS_FREEING 0x8 + +/* Flag that is set when an async callback is executed. */ +#define REDIS_IN_CALLBACK 0x10 + +/* Flag that is set when the async context has one or more subscriptions. */ +#define REDIS_SUBSCRIBED 0x20 + +/* Flag that is set when monitor mode is active */ +#define REDIS_MONITORING 0x40 + +/* Flag that is set when we should set SO_REUSEADDR before calling bind() */ +#define REDIS_REUSEADDR 0x80 + +#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ + +/* number of times we retry to connect in the case of EADDRNOTAVAIL and + * SO_REUSEADDR is being used. */ +#define REDIS_CONNECT_RETRIES 10 + +/* strerror_r has two completely different prototypes and behaviors + * depending on system issues, so we need to operate on the error buffer + * differently depending on which strerror_r we're using. */ +#ifndef _GNU_SOURCE +/* "regular" POSIX strerror_r that does the right thing. */ +#define __redis_strerror_r(errno, buf, len) \ + do { \ + strerror_r((errno), (buf), (len)); \ + } while (0) +#else +/* "bad" GNU strerror_r we need to clean up after. */ +#define __redis_strerror_r(errno, buf, len) \ + do { \ + char *err_str = strerror_r((errno), (buf), (len)); \ + /* If return value _isn't_ the start of the buffer we passed in, \ + * then GNU strerror_r returned an internal static buffer and we \ + * need to copy the result into our private buffer. */ \ + if (err_str != (buf)) { \ + buf[(len)] = '\0'; \ + strncat((buf), err_str, ((len) - 1)); \ + } \ + } while (0) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is the reply object returned by redisCommand() */ +typedef struct redisReply { + int type; /* REDIS_REPLY_* */ + long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ + int len; /* Length of string */ + char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ + size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ + struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ +} redisReply; + +redisReader *redisReaderCreate(void); + +/* Function to free the reply objects hiredis returns by default. */ +void freeReplyObject(void *reply); + +/* Functions to format a command according to the protocol. */ +int redisvFormatCommand(char **target, const char *format, va_list ap); +int redisFormatCommand(char **target, const char *format, ...); +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); +int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen); +void redisFreeCommand(char *cmd); +void redisFreeSdsCommand(sds cmd); + +enum redisConnectionType { + REDIS_CONN_TCP, + REDIS_CONN_UNIX, +}; + +/* Context for a connection to Redis */ +typedef struct redisContext { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + int fd; + int flags; + char *obuf; /* Write buffer */ + redisReader *reader; /* Protocol reader */ + + enum redisConnectionType connection_type; + struct timeval *timeout; + + struct { + char *host; + char *source_addr; + int port; + } tcp; + + struct { + char *path; + } unix_sock; +} redisContext; + +redisContext *redisConnect(const char *ip, int port); +redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); +redisContext *redisConnectNonBlock(const char *ip, int port); +redisContext *redisConnectBindNonBlock(const char *ip, int port, + const char *source_addr); +redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, + const char *source_addr); +redisContext *redisConnectUnix(const char *path); +redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); +redisContext *redisConnectUnixNonBlock(const char *path); +redisContext *redisConnectFd(int fd); + +/** + * Reconnect the given context using the saved information. + * + * This re-uses the exact same connect options as in the initial connection. + * host, ip (or path), timeout and bind address are reused, + * flags are used unmodified from the existing context. + * + * Returns REDIS_OK on successfull connect or REDIS_ERR otherwise. + */ +int redisReconnect(redisContext *c); + +int redisSetTimeout(redisContext *c, const struct timeval tv); +int redisEnableKeepAlive(redisContext *c); +void redisFree(redisContext *c); +int redisFreeKeepFd(redisContext *c); +int redisBufferRead(redisContext *c); +int redisBufferWrite(redisContext *c, int *done); + +/* In a blocking context, this function first checks if there are unconsumed + * replies to return and returns one if so. Otherwise, it flushes the output + * buffer to the socket and reads until it has a reply. In a non-blocking + * context, it will return unconsumed replies until there are no more. */ +int redisGetReply(redisContext *c, void **reply); +int redisGetReplyFromReader(redisContext *c, void **reply); + +/* Write a formatted command to the output buffer. Use these functions in blocking mode + * to get a pipeline of commands. */ +int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len); + +/* Write a command to the output buffer. Use these functions in blocking mode + * to get a pipeline of commands. */ +int redisvAppendCommand(redisContext *c, const char *format, va_list ap); +int redisAppendCommand(redisContext *c, const char *format, ...); +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); + +/* Issue a command to Redis. In a blocking context, it is identical to calling + * redisAppendCommand, followed by redisGetReply. The function will return + * NULL if there was an error in performing the request, otherwise it will + * return the reply. In a non-blocking context, it is identical to calling + * only redisAppendCommand and will always return NULL. */ +void *redisvCommand(redisContext *c, const char *format, va_list ap); +void *redisCommand(redisContext *c, const char *format, ...); +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-vip-0.3.0/hiutil.c b/ext/hiredis-vip-0.3.0/hiutil.c new file mode 100644 index 000000000..d10cdacea --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hiutil.c @@ -0,0 +1,554 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + + +#include "hiutil.h" + +#ifdef HI_HAVE_BACKTRACE +# include +#endif + +int +hi_set_blocking(int sd) +{ + int flags; + + flags = fcntl(sd, F_GETFL, 0); + if (flags < 0) { + return flags; + } + + return fcntl(sd, F_SETFL, flags & ~O_NONBLOCK); +} + +int +hi_set_nonblocking(int sd) +{ + int flags; + + flags = fcntl(sd, F_GETFL, 0); + if (flags < 0) { + return flags; + } + + return fcntl(sd, F_SETFL, flags | O_NONBLOCK); +} + +int +hi_set_reuseaddr(int sd) +{ + int reuse; + socklen_t len; + + reuse = 1; + len = sizeof(reuse); + + return setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &reuse, len); +} + +/* + * Disable Nagle algorithm on TCP socket. + * + * This option helps to minimize transmit latency by disabling coalescing + * of data to fill up a TCP segment inside the kernel. Sockets with this + * option must use readv() or writev() to do data transfer in bulk and + * hence avoid the overhead of small packets. + */ +int +hi_set_tcpnodelay(int sd) +{ + int nodelay; + socklen_t len; + + nodelay = 1; + len = sizeof(nodelay); + + return setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, &nodelay, len); +} + +int +hi_set_linger(int sd, int timeout) +{ + struct linger linger; + socklen_t len; + + linger.l_onoff = 1; + linger.l_linger = timeout; + + len = sizeof(linger); + + return setsockopt(sd, SOL_SOCKET, SO_LINGER, &linger, len); +} + +int +hi_set_sndbuf(int sd, int size) +{ + socklen_t len; + + len = sizeof(size); + + return setsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, len); +} + +int +hi_set_rcvbuf(int sd, int size) +{ + socklen_t len; + + len = sizeof(size); + + return setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, len); +} + +int +hi_get_soerror(int sd) +{ + int status, err; + socklen_t len; + + err = 0; + len = sizeof(err); + + status = getsockopt(sd, SOL_SOCKET, SO_ERROR, &err, &len); + if (status == 0) { + errno = err; + } + + return status; +} + +int +hi_get_sndbuf(int sd) +{ + int status, size; + socklen_t len; + + size = 0; + len = sizeof(size); + + status = getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, &len); + if (status < 0) { + return status; + } + + return size; +} + +int +hi_get_rcvbuf(int sd) +{ + int status, size; + socklen_t len; + + size = 0; + len = sizeof(size); + + status = getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, &len); + if (status < 0) { + return status; + } + + return size; +} + +int +_hi_atoi(uint8_t *line, size_t n) +{ + int value; + + if (n == 0) { + return -1; + } + + for (value = 0; n--; line++) { + if (*line < '0' || *line > '9') { + return -1; + } + + value = value * 10 + (*line - '0'); + } + + if (value < 0) { + return -1; + } + + return value; +} + +void +_hi_itoa(uint8_t *s, int num) +{ + uint8_t c; + uint8_t sign = 0; + + if(s == NULL) + { + return; + } + + uint32_t len, i; + len = 0; + + if(num < 0) + { + sign = 1; + num = abs(num); + } + else if(num == 0) + { + s[len++] = '0'; + return; + } + + while(num % 10 || num /10) + { + c = num %10 + '0'; + num = num /10; + s[len+1] = s[len]; + s[len] = c; + len ++; + } + + if(sign == 1) + { + s[len++] = '-'; + } + + s[len] = '\0'; + + for(i = 0; i < len/2; i ++) + { + c = s[i]; + s[i] = s[len - i -1]; + s[len - i -1] = c; + } + +} + + +int +hi_valid_port(int n) +{ + if (n < 1 || n > UINT16_MAX) { + return 0; + } + + return 1; +} + +int _uint_len(uint32_t num) +{ + int n = 0; + + if(num == 0) + { + return 1; + } + + while(num != 0) + { + n ++; + num /= 10; + } + + return n; +} + +void * +_hi_alloc(size_t size, const char *name, int line) +{ + void *p; + + ASSERT(size != 0); + + p = malloc(size); + + if(name == NULL && line == 1) + { + + } + + return p; +} + +void * +_hi_zalloc(size_t size, const char *name, int line) +{ + void *p; + + p = _hi_alloc(size, name, line); + if (p != NULL) { + memset(p, 0, size); + } + + return p; +} + +void * +_hi_calloc(size_t nmemb, size_t size, const char *name, int line) +{ + return _hi_zalloc(nmemb * size, name, line); +} + +void * +_hi_realloc(void *ptr, size_t size, const char *name, int line) +{ + void *p; + + ASSERT(size != 0); + + p = realloc(ptr, size); + + if(name == NULL && line == 1) + { + + } + + return p; +} + +void +_hi_free(void *ptr, const char *name, int line) +{ + ASSERT(ptr != NULL); + + if(name == NULL && line == 1) + { + + } + + free(ptr); +} + +void +hi_stacktrace(int skip_count) +{ + if(skip_count > 0) + { + + } + +#ifdef HI_HAVE_BACKTRACE + void *stack[64]; + char **symbols; + int size, i, j; + + size = backtrace(stack, 64); + symbols = backtrace_symbols(stack, size); + if (symbols == NULL) { + return; + } + + skip_count++; /* skip the current frame also */ + + for (i = skip_count, j = 0; i < size; i++, j++) { + printf("[%d] %s\n", j, symbols[i]); + } + + free(symbols); +#endif +} + +void +hi_stacktrace_fd(int fd) +{ + if(fd > 0) + { + + } +#ifdef HI_HAVE_BACKTRACE + void *stack[64]; + int size; + + size = backtrace(stack, 64); + backtrace_symbols_fd(stack, size, fd); +#endif +} + +void +hi_assert(const char *cond, const char *file, int line, int panic) +{ + + printf("File: %s Line: %d: %s\n", file, line, cond); + + if (panic) { + hi_stacktrace(1); + abort(); + } + abort(); +} + +int +_vscnprintf(char *buf, size_t size, const char *fmt, va_list args) +{ + int n; + + n = vsnprintf(buf, size, fmt, args); + + /* + * The return value is the number of characters which would be written + * into buf not including the trailing '\0'. If size is == 0 the + * function returns 0. + * + * On error, the function also returns 0. This is to allow idiom such + * as len += _vscnprintf(...) + * + * See: http://lwn.net/Articles/69419/ + */ + if (n <= 0) { + return 0; + } + + if (n < (int) size) { + return n; + } + + return (int)(size - 1); +} + +int +_scnprintf(char *buf, size_t size, const char *fmt, ...) +{ + va_list args; + int n; + + va_start(args, fmt); + n = _vscnprintf(buf, size, fmt, args); + va_end(args); + + return n; +} + +/* + * Send n bytes on a blocking descriptor + */ +ssize_t +_hi_sendn(int sd, const void *vptr, size_t n) +{ + size_t nleft; + ssize_t nsend; + const char *ptr; + + ptr = vptr; + nleft = n; + while (nleft > 0) { + nsend = send(sd, ptr, nleft, 0); + if (nsend < 0) { + if (errno == EINTR) { + continue; + } + return nsend; + } + if (nsend == 0) { + return -1; + } + + nleft -= (size_t)nsend; + ptr += nsend; + } + + return (ssize_t)n; +} + +/* + * Recv n bytes from a blocking descriptor + */ +ssize_t +_hi_recvn(int sd, void *vptr, size_t n) +{ + size_t nleft; + ssize_t nrecv; + char *ptr; + + ptr = vptr; + nleft = n; + while (nleft > 0) { + nrecv = recv(sd, ptr, nleft, 0); + if (nrecv < 0) { + if (errno == EINTR) { + continue; + } + return nrecv; + } + if (nrecv == 0) { + break; + } + + nleft -= (size_t)nrecv; + ptr += nrecv; + } + + return (ssize_t)(n - nleft); +} + +/* + * Return the current time in microseconds since Epoch + */ +int64_t +hi_usec_now(void) +{ + struct timeval now; + int64_t usec; + int status; + + status = gettimeofday(&now, NULL); + if (status < 0) { + return -1; + } + + usec = (int64_t)now.tv_sec * 1000000LL + (int64_t)now.tv_usec; + + return usec; +} + +/* + * Return the current time in milliseconds since Epoch + */ +int64_t +hi_msec_now(void) +{ + return hi_usec_now() / 1000LL; +} + +void print_string_with_length(char *s, size_t len) +{ + char *token; + for(token = s; token <= s + len; token ++) + { + printf("%c", *token); + } + printf("\n"); +} + +void print_string_with_length_fix_CRLF(char *s, size_t len) +{ + char *token; + for(token = s; token < s + len; token ++) + { + if(*token == CR) + { + printf("\\r"); + } + else if(*token == LF) + { + printf("\\n"); + } + else + { + printf("%c", *token); + } + } + printf("\n"); +} + diff --git a/ext/hiredis-vip-0.3.0/hiutil.h b/ext/hiredis-vip-0.3.0/hiutil.h new file mode 100644 index 000000000..d9e1ddb0b --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hiutil.h @@ -0,0 +1,265 @@ +#ifndef __HIUTIL_H_ +#define __HIUTIL_H_ + +#include +#include +#include + +#define HI_OK 0 +#define HI_ERROR -1 +#define HI_EAGAIN -2 +#define HI_ENOMEM -3 + +typedef int rstatus_t; /* return type */ + +#define LF (uint8_t) 10 +#define CR (uint8_t) 13 +#define CRLF "\x0d\x0a" +#define CRLF_LEN (sizeof("\x0d\x0a") - 1) + +#define NELEMS(a) ((sizeof(a)) / sizeof((a)[0])) + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#define SQUARE(d) ((d) * (d)) +#define VAR(s, s2, n) (((n) < 2) ? 0.0 : ((s2) - SQUARE(s)/(n)) / ((n) - 1)) +#define STDDEV(s, s2, n) (((n) < 2) ? 0.0 : sqrt(VAR((s), (s2), (n)))) + +#define HI_INET4_ADDRSTRLEN (sizeof("255.255.255.255") - 1) +#define HI_INET6_ADDRSTRLEN \ + (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1) +#define HI_INET_ADDRSTRLEN MAX(HI_INET4_ADDRSTRLEN, HI_INET6_ADDRSTRLEN) +#define HI_UNIX_ADDRSTRLEN \ + (sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path)) + +#define HI_MAXHOSTNAMELEN 256 + +/* + * Length of 1 byte, 2 bytes, 4 bytes, 8 bytes and largest integral + * type (uintmax_t) in ascii, including the null terminator '\0' + * + * From stdint.h, we have: + * # define UINT8_MAX (255) + * # define UINT16_MAX (65535) + * # define UINT32_MAX (4294967295U) + * # define UINT64_MAX (__UINT64_C(18446744073709551615)) + */ +#define HI_UINT8_MAXLEN (3 + 1) +#define HI_UINT16_MAXLEN (5 + 1) +#define HI_UINT32_MAXLEN (10 + 1) +#define HI_UINT64_MAXLEN (20 + 1) +#define HI_UINTMAX_MAXLEN HI_UINT64_MAXLEN + +/* + * Make data 'd' or pointer 'p', n-byte aligned, where n is a power of 2 + * of 2. + */ +#define HI_ALIGNMENT sizeof(unsigned long) /* platform word */ +#define HI_ALIGN(d, n) (((d) + (n - 1)) & ~(n - 1)) +#define HI_ALIGN_PTR(p, n) \ + (void *) (((uintptr_t) (p) + ((uintptr_t) n - 1)) & ~((uintptr_t) n - 1)) + + + +#define str3icmp(m, c0, c1, c2) \ + ((m[0] == c0 || m[0] == (c0 ^ 0x20)) && \ + (m[1] == c1 || m[1] == (c1 ^ 0x20)) && \ + (m[2] == c2 || m[2] == (c2 ^ 0x20))) + +#define str4icmp(m, c0, c1, c2, c3) \ + (str3icmp(m, c0, c1, c2) && (m[3] == c3 || m[3] == (c3 ^ 0x20))) + +#define str5icmp(m, c0, c1, c2, c3, c4) \ + (str4icmp(m, c0, c1, c2, c3) && (m[4] == c4 || m[4] == (c4 ^ 0x20))) + +#define str6icmp(m, c0, c1, c2, c3, c4, c5) \ + (str5icmp(m, c0, c1, c2, c3, c4) && (m[5] == c5 || m[5] == (c5 ^ 0x20))) + +#define str7icmp(m, c0, c1, c2, c3, c4, c5, c6) \ + (str6icmp(m, c0, c1, c2, c3, c4, c5) && \ + (m[6] == c6 || m[6] == (c6 ^ 0x20))) + +#define str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ + (str7icmp(m, c0, c1, c2, c3, c4, c5, c6) && \ + (m[7] == c7 || m[7] == (c7 ^ 0x20))) + +#define str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \ + (str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && \ + (m[8] == c8 || m[8] == (c8 ^ 0x20))) + +#define str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) \ + (str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) && \ + (m[9] == c9 || m[9] == (c9 ^ 0x20))) + +#define str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) \ + (str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) && \ + (m[10] == c10 || m[10] == (c10 ^ 0x20))) + +#define str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) \ + (str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) && \ + (m[11] == c11 || m[11] == (c11 ^ 0x20))) + +#define str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) \ + (str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) && \ + (m[12] == c12 || m[12] == (c12 ^ 0x20))) + +#define str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) \ + (str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) && \ + (m[13] == c13 || m[13] == (c13 ^ 0x20))) + +#define str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) \ + (str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) && \ + (m[14] == c14 || m[14] == (c14 ^ 0x20))) + +#define str16icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15) \ + (str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) && \ + (m[15] == c15 || m[15] == (c15 ^ 0x20))) + + + +/* + * Wrapper to workaround well known, safe, implicit type conversion when + * invoking system calls. + */ +#define hi_gethostname(_name, _len) \ + gethostname((char *)_name, (size_t)_len) + +#define hi_atoi(_line, _n) \ + _hi_atoi((uint8_t *)_line, (size_t)_n) +#define hi_itoa(_line, _n) \ + _hi_itoa((uint8_t *)_line, (int)_n) + +#define uint_len(_n) \ + _uint_len((uint32_t)_n) + + +int hi_set_blocking(int sd); +int hi_set_nonblocking(int sd); +int hi_set_reuseaddr(int sd); +int hi_set_tcpnodelay(int sd); +int hi_set_linger(int sd, int timeout); +int hi_set_sndbuf(int sd, int size); +int hi_set_rcvbuf(int sd, int size); +int hi_get_soerror(int sd); +int hi_get_sndbuf(int sd); +int hi_get_rcvbuf(int sd); + +int _hi_atoi(uint8_t *line, size_t n); +void _hi_itoa(uint8_t *s, int num); + +int hi_valid_port(int n); + +int _uint_len(uint32_t num); + + +/* + * Memory allocation and free wrappers. + * + * These wrappers enables us to loosely detect double free, dangling + * pointer access and zero-byte alloc. + */ +#define hi_alloc(_s) \ + _hi_alloc((size_t)(_s), __FILE__, __LINE__) + +#define hi_zalloc(_s) \ + _hi_zalloc((size_t)(_s), __FILE__, __LINE__) + +#define hi_calloc(_n, _s) \ + _hi_calloc((size_t)(_n), (size_t)(_s), __FILE__, __LINE__) + +#define hi_realloc(_p, _s) \ + _hi_realloc(_p, (size_t)(_s), __FILE__, __LINE__) + +#define hi_free(_p) do { \ + _hi_free(_p, __FILE__, __LINE__); \ + (_p) = NULL; \ +} while (0) + +void *_hi_alloc(size_t size, const char *name, int line); +void *_hi_zalloc(size_t size, const char *name, int line); +void *_hi_calloc(size_t nmemb, size_t size, const char *name, int line); +void *_hi_realloc(void *ptr, size_t size, const char *name, int line); +void _hi_free(void *ptr, const char *name, int line); + + +#define hi_strndup(_s, _n) \ + strndup((char *)(_s), (size_t)(_n)); + +/* + * Wrappers to send or receive n byte message on a blocking + * socket descriptor. + */ +#define hi_sendn(_s, _b, _n) \ + _hi_sendn(_s, _b, (size_t)(_n)) + +#define hi_recvn(_s, _b, _n) \ + _hi_recvn(_s, _b, (size_t)(_n)) + +/* + * Wrappers to read or write data to/from (multiple) buffers + * to a file or socket descriptor. + */ +#define hi_read(_d, _b, _n) \ + read(_d, _b, (size_t)(_n)) + +#define hi_readv(_d, _b, _n) \ + readv(_d, _b, (int)(_n)) + +#define hi_write(_d, _b, _n) \ + write(_d, _b, (size_t)(_n)) + +#define hi_writev(_d, _b, _n) \ + writev(_d, _b, (int)(_n)) + +ssize_t _hi_sendn(int sd, const void *vptr, size_t n); +ssize_t _hi_recvn(int sd, void *vptr, size_t n); + +/* + * Wrappers for defining custom assert based on whether macro + * HI_ASSERT_PANIC or HI_ASSERT_LOG was defined at the moment + * ASSERT was called. + */ +#ifdef HI_ASSERT_PANIC + +#define ASSERT(_x) do { \ + if (!(_x)) { \ + hi_assert(#_x, __FILE__, __LINE__, 1); \ + } \ +} while (0) + +#define NOT_REACHED() ASSERT(0) + +#elif HI_ASSERT_LOG + +#define ASSERT(_x) do { \ + if (!(_x)) { \ + hi_assert(#_x, __FILE__, __LINE__, 0); \ + } \ +} while (0) + +#define NOT_REACHED() ASSERT(0) + +#else + +#define ASSERT(_x) + +#define NOT_REACHED() + +#endif + +void hi_assert(const char *cond, const char *file, int line, int panic); +void hi_stacktrace(int skip_count); +void hi_stacktrace_fd(int fd); + +int _scnprintf(char *buf, size_t size, const char *fmt, ...); +int _vscnprintf(char *buf, size_t size, const char *fmt, va_list args); +int64_t hi_usec_now(void); +int64_t hi_msec_now(void); + +void print_string_with_length(char *s, size_t len); +void print_string_with_length_fix_CRLF(char *s, size_t len); + +uint16_t crc16(const char *buf, int len); + +#endif diff --git a/ext/hiredis-vip-0.3.0/net.c b/ext/hiredis-vip-0.3.0/net.c new file mode 100644 index 000000000..60a2dc754 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/net.c @@ -0,0 +1,458 @@ +/* Extracted from anet.c to work properly with Hiredis error reporting. + * + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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 "fmacros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "net.h" +#include "sds.h" + +/* Defined in hiredis.c */ +void __redisSetError(redisContext *c, int type, const char *str); + +static void redisContextCloseFd(redisContext *c) { + if (c && c->fd >= 0) { + close(c->fd); + c->fd = -1; + } +} + +static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { + char buf[128] = { 0 }; + size_t len = 0; + + if (prefix != NULL) + len = snprintf(buf,sizeof(buf),"%s: ",prefix); + __redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len); + __redisSetError(c,type,buf); +} + +static int redisSetReuseAddr(redisContext *c) { + int on = 1; + if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +static int redisCreateSocket(redisContext *c, int type) { + int s; + if ((s = socket(type, SOCK_STREAM, 0)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + c->fd = s; + if (type == AF_INET) { + if (redisSetReuseAddr(c) == REDIS_ERR) { + return REDIS_ERR; + } + } + return REDIS_OK; +} + +static int redisSetBlocking(redisContext *c, int blocking) { + int flags; + + /* Set the socket nonblocking. + * Note that fcntl(2) for F_GETFL and F_SETFL can't be + * interrupted by a signal. */ + if ((flags = fcntl(c->fd, F_GETFL)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + + if (blocking) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if (fcntl(c->fd, F_SETFL, flags) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +int redisKeepAlive(redisContext *c, int interval) { + int val = 1; + int fd = c->fd; + + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = interval; + +#ifdef _OSX + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#else +#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) + val = interval; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = interval/3; + if (val == 0) val = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = 3; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#endif +#endif + + return REDIS_OK; +} + +static int redisSetTcpNoDelay(redisContext *c) { + int yes = 1; + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) + +static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) { + struct pollfd wfd[1]; + long msec; + + msec = -1; + wfd[0].fd = c->fd; + wfd[0].events = POLLOUT; + + /* Only use timeout when not NULL. */ + if (timeout != NULL) { + if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { + __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); + redisContextCloseFd(c); + return REDIS_ERR; + } + + msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); + + if (msec < 0 || msec > INT_MAX) { + msec = INT_MAX; + } + } + + if (errno == EINPROGRESS) { + int res; + + if ((res = poll(wfd, 1, msec)) == -1) { + __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); + redisContextCloseFd(c); + return REDIS_ERR; + } else if (res == 0) { + errno = ETIMEDOUT; + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; + } + + if (redisCheckSocketError(c) != REDIS_OK) + return REDIS_ERR; + + return REDIS_OK; + } + + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; +} + +int redisCheckSocketError(redisContext *c) { + int err = 0; + socklen_t errlen = sizeof(err); + + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); + return REDIS_ERR; + } + + if (err) { + errno = err; + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + + return REDIS_OK; +} + +int redisContextSetTimeout(redisContext *c, const struct timeval tv) { + if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); + return REDIS_ERR; + } + if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); + return REDIS_ERR; + } + return REDIS_OK; +} + +static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr) { + int s, rv, n; + char _port[6]; /* strlen("65535"); */ + struct addrinfo hints, *servinfo, *bservinfo, *p, *b; + int blocking = (c->flags & REDIS_BLOCK); + int reuseaddr = (c->flags & REDIS_REUSEADDR); + int reuses = 0; + + c->connection_type = REDIS_CONN_TCP; + c->tcp.port = port; + + /* We need to take possession of the passed parameters + * to make them reusable for a reconnect. + * We also carefully check we don't free data we already own, + * as in the case of the reconnect method. + * + * This is a bit ugly, but atleast it works and doesn't leak memory. + **/ + if (c->tcp.host != addr) { + if (c->tcp.host) + free(c->tcp.host); + + c->tcp.host = strdup(addr); + } + + if (timeout) { + if (c->timeout != timeout) { + if (c->timeout == NULL) + c->timeout = malloc(sizeof(struct timeval)); + + memcpy(c->timeout, timeout, sizeof(struct timeval)); + } + } else { + if (c->timeout) + free(c->timeout); + c->timeout = NULL; + } + + if (source_addr == NULL) { + free(c->tcp.source_addr); + c->tcp.source_addr = NULL; + } else if (c->tcp.source_addr != source_addr) { + free(c->tcp.source_addr); + c->tcp.source_addr = strdup(source_addr); + } + + snprintf(_port, 6, "%d", port); + memset(&hints,0,sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + /* Try with IPv6 if no IPv4 address was found. We do it in this order since + * in a Redis client you can't afford to test if you have IPv6 connectivity + * as this would add latency to every connect. Otherwise a more sensible + * route could be: Use IPv6 if both addresses are available and there is IPv6 + * connectivity. */ + if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) { + hints.ai_family = AF_INET6; + if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { + __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); + return REDIS_ERR; + } + } + for (p = servinfo; p != NULL; p = p->ai_next) { +addrretry: + if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) + continue; + + c->fd = s; + if (redisSetBlocking(c,0) != REDIS_OK) + goto error; + if (c->tcp.source_addr) { + int bound = 0; + /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ + if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + + if (reuseaddr) { + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, + sizeof(n)) < 0) { + goto error; + } + } + + for (b = bservinfo; b != NULL; b = b->ai_next) { + if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { + bound = 1; + break; + } + } + freeaddrinfo(bservinfo); + if (!bound) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + } + if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { + if (errno == EHOSTUNREACH) { + redisContextCloseFd(c); + continue; + } else if (errno == EINPROGRESS && !blocking) { + /* This is ok. */ + } else if (errno == EADDRNOTAVAIL && reuseaddr) { + if (++reuses >= REDIS_CONNECT_RETRIES) { + goto error; + } else { + goto addrretry; + } + } else { + if (redisContextWaitReady(c,c->timeout) != REDIS_OK) + goto error; + } + } + if (blocking && redisSetBlocking(c,1) != REDIS_OK) + goto error; + if (redisSetTcpNoDelay(c) != REDIS_OK) + goto error; + + c->flags |= REDIS_CONNECTED; + rv = REDIS_OK; + goto end; + } + if (p == NULL) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + +error: + rv = REDIS_ERR; +end: + freeaddrinfo(servinfo); + return rv; // Need to return REDIS_OK if alright +} + +int redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout) { + return _redisContextConnectTcp(c, addr, port, timeout, NULL); +} + +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr) { + return _redisContextConnectTcp(c, addr, port, timeout, source_addr); +} + +int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { + int blocking = (c->flags & REDIS_BLOCK); + struct sockaddr_un sa; + + if (redisCreateSocket(c,AF_LOCAL) < 0) + return REDIS_ERR; + if (redisSetBlocking(c,0) != REDIS_OK) + return REDIS_ERR; + + c->connection_type = REDIS_CONN_UNIX; + if (c->unix_sock.path != path) + c->unix_sock.path = strdup(path); + + if (timeout) { + if (c->timeout != timeout) { + if (c->timeout == NULL) + c->timeout = malloc(sizeof(struct timeval)); + + memcpy(c->timeout, timeout, sizeof(struct timeval)); + } + } else { + if (c->timeout) + free(c->timeout); + c->timeout = NULL; + } + + sa.sun_family = AF_LOCAL; + strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); + if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { + if (errno == EINPROGRESS && !blocking) { + /* This is ok. */ + } else { + if (redisContextWaitReady(c,c->timeout) != REDIS_OK) + return REDIS_ERR; + } + } + + /* Reset socket to be blocking after connect(2). */ + if (blocking && redisSetBlocking(c,1) != REDIS_OK) + return REDIS_ERR; + + c->flags |= REDIS_CONNECTED; + return REDIS_OK; +} diff --git a/ext/hiredis-vip-0.3.0/net.h b/ext/hiredis-vip-0.3.0/net.h new file mode 100644 index 000000000..2f1a0bf85 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/net.h @@ -0,0 +1,53 @@ +/* Extracted from anet.c to work properly with Hiredis error reporting. + * + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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. + */ + +#ifndef __NET_H +#define __NET_H + +#include "hiredis.h" + +#if defined(__sun) +#define AF_LOCAL AF_UNIX +#endif + +int redisCheckSocketError(redisContext *c); +int redisContextSetTimeout(redisContext *c, const struct timeval tv); +int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr); +int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); +int redisKeepAlive(redisContext *c, int interval); + +#endif diff --git a/ext/hiredis-vip-0.3.0/read.c b/ext/hiredis-vip-0.3.0/read.c new file mode 100644 index 000000000..df1a467a9 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/read.c @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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 "fmacros.h" +#include +#include +#ifndef _MSC_VER +#include +#endif +#include +#include +#include + +#include "read.h" +#include "sds.h" + +static void __redisReaderSetError(redisReader *r, int type, const char *str) { + size_t len; + + if (r->reply != NULL && r->fn && r->fn->freeObject) { + r->fn->freeObject(r->reply); + r->reply = NULL; + } + + /* Clear input buffer on errors. */ + if (r->buf != NULL) { + sdsfree(r->buf); + r->buf = NULL; + r->pos = r->len = 0; + } + + /* Reset task stack. */ + r->ridx = -1; + + /* Set error. */ + r->err = type; + len = strlen(str); + len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); + memcpy(r->errstr,str,len); + r->errstr[len] = '\0'; +} + +static size_t chrtos(char *buf, size_t size, char byte) { + size_t len = 0; + + switch(byte) { + case '\\': + case '"': + len = snprintf(buf,size,"\"\\%c\"",byte); + break; + case '\n': len = snprintf(buf,size,"\"\\n\""); break; + case '\r': len = snprintf(buf,size,"\"\\r\""); break; + case '\t': len = snprintf(buf,size,"\"\\t\""); break; + case '\a': len = snprintf(buf,size,"\"\\a\""); break; + case '\b': len = snprintf(buf,size,"\"\\b\""); break; + default: + if (isprint(byte)) + len = snprintf(buf,size,"\"%c\"",byte); + else + len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); + break; + } + + return len; +} + +static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) { + char cbuf[8], sbuf[128]; + + chrtos(cbuf,sizeof(cbuf),byte); + snprintf(sbuf,sizeof(sbuf), + "Protocol error, got %s as reply type byte", cbuf); + __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf); +} + +static void __redisReaderSetErrorOOM(redisReader *r) { + __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory"); +} + +static char *readBytes(redisReader *r, unsigned int bytes) { + char *p; + if (r->len-r->pos >= bytes) { + p = r->buf+r->pos; + r->pos += bytes; + return p; + } + return NULL; +} + +/* Find pointer to \r\n. */ +static char *seekNewline(char *s, size_t len) { + int pos = 0; + int _len = len-1; + + /* Position should be < len-1 because the character at "pos" should be + * followed by a \n. Note that strchr cannot be used because it doesn't + * allow to search a limited length and the buffer that is being searched + * might not have a trailing NULL character. */ + while (pos < _len) { + while(pos < _len && s[pos] != '\r') pos++; + if (s[pos] != '\r') { + /* Not found. */ + return NULL; + } else { + if (s[pos+1] == '\n') { + /* Found. */ + return s+pos; + } else { + /* Continue searching. */ + pos++; + } + } + } + return NULL; +} + +/* Read a long long value starting at *s, under the assumption that it will be + * terminated by \r\n. Ambiguously returns -1 for unexpected input. */ +static long long readLongLong(char *s) { + long long v = 0; + int dec, mult = 1; + char c; + + if (*s == '-') { + mult = -1; + s++; + } else if (*s == '+') { + mult = 1; + s++; + } + + while ((c = *(s++)) != '\r') { + dec = c - '0'; + if (dec >= 0 && dec < 10) { + v *= 10; + v += dec; + } else { + /* Should not happen... */ + return -1; + } + } + + return mult*v; +} + +static char *readLine(redisReader *r, int *_len) { + char *p, *s; + int len; + + p = r->buf+r->pos; + s = seekNewline(p,(r->len-r->pos)); + if (s != NULL) { + len = s-(r->buf+r->pos); + r->pos += len+2; /* skip \r\n */ + if (_len) *_len = len; + return p; + } + return NULL; +} + +static void moveToNextTask(redisReader *r) { + redisReadTask *cur, *prv; + while (r->ridx >= 0) { + /* Return a.s.a.p. when the stack is now empty. */ + if (r->ridx == 0) { + r->ridx--; + return; + } + + cur = &(r->rstack[r->ridx]); + prv = &(r->rstack[r->ridx-1]); + assert(prv->type == REDIS_REPLY_ARRAY); + if (cur->idx == prv->elements-1) { + r->ridx--; + } else { + /* Reset the type because the next item can be anything */ + assert(cur->idx < prv->elements); + cur->type = -1; + cur->elements = -1; + cur->idx++; + return; + } + } +} + +static int processLineItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj; + char *p; + int len; + + if ((p = readLine(r,&len)) != NULL) { + if (cur->type == REDIS_REPLY_INTEGER) { + if (r->fn && r->fn->createInteger) + obj = r->fn->createInteger(cur,readLongLong(p)); + else + obj = (void*)REDIS_REPLY_INTEGER; + } else { + /* Type will be error or status. */ + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,p,len); + else + obj = (void*)(size_t)(cur->type); + } + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + /* Set reply if this is the root object. */ + if (r->ridx == 0) r->reply = obj; + moveToNextTask(r); + return REDIS_OK; + } + + return REDIS_ERR; +} + +static int processBulkItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj = NULL; + char *p, *s; + long len; + unsigned long bytelen; + int success = 0; + + p = r->buf+r->pos; + s = seekNewline(p,r->len-r->pos); + if (s != NULL) { + p = r->buf+r->pos; + bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ + len = readLongLong(p); + + if (len < 0) { + /* The nil object can always be created. */ + if (r->fn && r->fn->createNil) + obj = r->fn->createNil(cur); + else + obj = (void*)REDIS_REPLY_NIL; + success = 1; + } else { + /* Only continue when the buffer contains the entire bulk item. */ + bytelen += len+2; /* include \r\n */ + if (r->pos+bytelen <= r->len) { + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,s+2,len); + else + obj = (void*)REDIS_REPLY_STRING; + success = 1; + } + } + + /* Proceed when obj was created. */ + if (success) { + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + r->pos += bytelen; + + /* Set reply if this is the root object. */ + if (r->ridx == 0) r->reply = obj; + moveToNextTask(r); + return REDIS_OK; + } + } + + return REDIS_ERR; +} + +static int processMultiBulkItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj; + char *p; + long elements; + int root = 0; + + /* Set error for nested multi bulks with depth > 7 */ + if (r->ridx == 8) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "No support for nested multi bulk replies with depth > 7"); + return REDIS_ERR; + } + + if ((p = readLine(r,NULL)) != NULL) { + elements = readLongLong(p); + root = (r->ridx == 0); + + if (elements == -1) { + if (r->fn && r->fn->createNil) + obj = r->fn->createNil(cur); + else + obj = (void*)REDIS_REPLY_NIL; + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + moveToNextTask(r); + } else { + if (r->fn && r->fn->createArray) + obj = r->fn->createArray(cur,elements); + else + obj = (void*)REDIS_REPLY_ARRAY; + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + /* Modify task stack when there are more than 0 elements. */ + if (elements > 0) { + cur->elements = elements; + cur->obj = obj; + r->ridx++; + r->rstack[r->ridx].type = -1; + r->rstack[r->ridx].elements = -1; + r->rstack[r->ridx].idx = 0; + r->rstack[r->ridx].obj = NULL; + r->rstack[r->ridx].parent = cur; + r->rstack[r->ridx].privdata = r->privdata; + } else { + moveToNextTask(r); + } + } + + /* Set reply if this is the root object. */ + if (root) r->reply = obj; + return REDIS_OK; + } + + return REDIS_ERR; +} + +static int processItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + char *p; + + /* check if we need to read type */ + if (cur->type < 0) { + if ((p = readBytes(r,1)) != NULL) { + switch (p[0]) { + case '-': + cur->type = REDIS_REPLY_ERROR; + break; + case '+': + cur->type = REDIS_REPLY_STATUS; + break; + case ':': + cur->type = REDIS_REPLY_INTEGER; + break; + case '$': + cur->type = REDIS_REPLY_STRING; + break; + case '*': + cur->type = REDIS_REPLY_ARRAY; + break; + default: + __redisReaderSetErrorProtocolByte(r,*p); + return REDIS_ERR; + } + } else { + /* could not consume 1 byte */ + return REDIS_ERR; + } + } + + /* process typed item */ + switch(cur->type) { + case REDIS_REPLY_ERROR: + case REDIS_REPLY_STATUS: + case REDIS_REPLY_INTEGER: + return processLineItem(r); + case REDIS_REPLY_STRING: + return processBulkItem(r); + case REDIS_REPLY_ARRAY: + return processMultiBulkItem(r); + default: + assert(NULL); + return REDIS_ERR; /* Avoid warning. */ + } +} + +redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { + redisReader *r; + + r = calloc(sizeof(redisReader),1); + if (r == NULL) + return NULL; + + r->err = 0; + r->errstr[0] = '\0'; + r->fn = fn; + r->buf = sdsempty(); + r->maxbuf = REDIS_READER_MAX_BUF; + if (r->buf == NULL) { + free(r); + return NULL; + } + + r->ridx = -1; + return r; +} + +void redisReaderFree(redisReader *r) { + if (r->reply != NULL && r->fn && r->fn->freeObject) + r->fn->freeObject(r->reply); + if (r->buf != NULL) + sdsfree(r->buf); + free(r); +} + +int redisReaderFeed(redisReader *r, const char *buf, size_t len) { + sds newbuf; + + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; + + /* Copy the provided buffer. */ + if (buf != NULL && len >= 1) { + /* Destroy internal buffer when it is empty and is quite large. */ + if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { + sdsfree(r->buf); + r->buf = sdsempty(); + r->pos = 0; + + /* r->buf should not be NULL since we just free'd a larger one. */ + assert(r->buf != NULL); + } + + newbuf = sdscatlen(r->buf,buf,len); + if (newbuf == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + r->buf = newbuf; + r->len = sdslen(r->buf); + } + + return REDIS_OK; +} + +int redisReaderGetReply(redisReader *r, void **reply) { + /* Default target pointer to NULL. */ + if (reply != NULL) + *reply = NULL; + + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; + + /* When the buffer is empty, there will never be a reply. */ + if (r->len == 0) + return REDIS_OK; + + /* Set first item to process when the stack is empty. */ + if (r->ridx == -1) { + r->rstack[0].type = -1; + r->rstack[0].elements = -1; + r->rstack[0].idx = -1; + r->rstack[0].obj = NULL; + r->rstack[0].parent = NULL; + r->rstack[0].privdata = r->privdata; + r->ridx = 0; + } + + /* Process items in reply. */ + while (r->ridx >= 0) + if (processItem(r) != REDIS_OK) + break; + + /* Return ASAP when an error occurred. */ + if (r->err) + return REDIS_ERR; + + /* Discard part of the buffer when we've consumed at least 1k, to avoid + * doing unnecessary calls to memmove() in sds.c. */ + if (r->pos >= 1024) { + sdsrange(r->buf,r->pos,-1); + r->pos = 0; + r->len = sdslen(r->buf); + } + + /* Emit a reply when there is one. */ + if (r->ridx == -1) { + if (reply != NULL) + *reply = r->reply; + r->reply = NULL; + } + return REDIS_OK; +} diff --git a/ext/hiredis-vip-0.3.0/read.h b/ext/hiredis-vip-0.3.0/read.h new file mode 100644 index 000000000..088c97903 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/read.h @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + + +#ifndef __HIREDIS_READ_H +#define __HIREDIS_READ_H +#include /* for size_t */ + +#define REDIS_ERR -1 +#define REDIS_OK 0 + +/* When an error occurs, the err flag in a context is set to hold the type of + * error that occured. REDIS_ERR_IO means there was an I/O error and you + * should use the "errno" variable to find out what is wrong. + * For other values, the "errstr" field will hold a description. */ +#define REDIS_ERR_IO 1 /* Error in read or write */ +#define REDIS_ERR_EOF 3 /* End of file */ +#define REDIS_ERR_PROTOCOL 4 /* Protocol error */ +#define REDIS_ERR_OOM 5 /* Out of memory */ +#define REDIS_ERR_OTHER 2 /* Everything else... */ +#if 1 //shenzheng 2015-8-10 redis cluster +#define REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT 6 +#endif //shenzheng 2015-8-10 redis cluster + +#define REDIS_REPLY_STRING 1 +#define REDIS_REPLY_ARRAY 2 +#define REDIS_REPLY_INTEGER 3 +#define REDIS_REPLY_NIL 4 +#define REDIS_REPLY_STATUS 5 +#define REDIS_REPLY_ERROR 6 + +#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ + +#if 1 //shenzheng 2015-8-22 redis cluster +#define REDIS_ERROR_MOVED "MOVED" +#define REDIS_ERROR_ASK "ASK" +#define REDIS_ERROR_TRYAGAIN "TRYAGAIN" +#define REDIS_ERROR_CROSSSLOT "CROSSSLOT" +#define REDIS_ERROR_CLUSTERDOWN "CLUSTERDOWN" + +#define REDIS_STATUS_OK "OK" +#endif //shenzheng 2015-9-24 redis cluster + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct redisReadTask { + int type; + int elements; /* number of elements in multibulk container */ + int idx; /* index in parent (array) object */ + void *obj; /* holds user-generated value for a read task */ + struct redisReadTask *parent; /* parent task */ + void *privdata; /* user-settable arbitrary field */ +} redisReadTask; + +typedef struct redisReplyObjectFunctions { + void *(*createString)(const redisReadTask*, char*, size_t); + void *(*createArray)(const redisReadTask*, int); + void *(*createInteger)(const redisReadTask*, long long); + void *(*createNil)(const redisReadTask*); + void (*freeObject)(void*); +} redisReplyObjectFunctions; + +typedef struct redisReader { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + + char *buf; /* Read buffer */ + size_t pos; /* Buffer cursor */ + size_t len; /* Buffer length */ + size_t maxbuf; /* Max length of unused buffer */ + + redisReadTask rstack[9]; + int ridx; /* Index of current read task */ + void *reply; /* Temporary reply pointer */ + + redisReplyObjectFunctions *fn; + void *privdata; +} redisReader; + +/* Public API for the protocol parser. */ +redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn); +void redisReaderFree(redisReader *r); +int redisReaderFeed(redisReader *r, const char *buf, size_t len); +int redisReaderGetReply(redisReader *r, void **reply); + +/* Backwards compatibility, can be removed on big version bump. */ +#define redisReplyReaderCreate redisReaderCreate +#define redisReplyReaderFree redisReaderFree +#define redisReplyReaderFeed redisReaderFeed +#define redisReplyReaderGetReply redisReaderGetReply +#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) +#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply) +#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-vip-0.3.0/sds.c b/ext/hiredis-vip-0.3.0/sds.c new file mode 100644 index 000000000..5d55b7792 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/sds.c @@ -0,0 +1,1095 @@ +/* SDS (Simple Dynamic Strings), A C dynamic strings library. + * + * Copyright (c) 2006-2014, Salvatore Sanfilippo + * 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 +#include +#include +#include +#include + +#include "sds.h" + +/* Create a new sds string with the content specified by the 'init' pointer + * and 'initlen'. + * If NULL is used for 'init' the string is initialized with zero bytes. + * + * The string is always null-termined (all the sds strings are, always) so + * even if you create an sds string with: + * + * mystring = sdsnewlen("abc",3"); + * + * You can print the string with printf() as there is an implicit \0 at the + * end of the string. However the string is binary safe and can contain + * \0 characters in the middle, as the length is stored in the sds header. */ +sds sdsnewlen(const void *init, size_t initlen) { + struct sdshdr *sh; + + if (init) { + sh = malloc(sizeof *sh+initlen+1); + } else { + sh = calloc(sizeof *sh+initlen+1,1); + } + if (sh == NULL) return NULL; + sh->len = initlen; + sh->free = 0; + if (initlen && init) + memcpy(sh->buf, init, initlen); + sh->buf[initlen] = '\0'; + return (char*)sh->buf; +} + +/* Create an empty (zero length) sds string. Even in this case the string + * always has an implicit null term. */ +sds sdsempty(void) { + return sdsnewlen("",0); +} + +/* Create a new sds string starting from a null termined C string. */ +sds sdsnew(const char *init) { + size_t initlen = (init == NULL) ? 0 : strlen(init); + return sdsnewlen(init, initlen); +} + +/* Duplicate an sds string. */ +sds sdsdup(const sds s) { + return sdsnewlen(s, sdslen(s)); +} + +/* Free an sds string. No operation is performed if 's' is NULL. */ +void sdsfree(sds s) { + if (s == NULL) return; + free(s-sizeof(struct sdshdr)); +} + +/* Set the sds string length to the length as obtained with strlen(), so + * considering as content only up to the first null term character. + * + * This function is useful when the sds string is hacked manually in some + * way, like in the following example: + * + * s = sdsnew("foobar"); + * s[2] = '\0'; + * sdsupdatelen(s); + * printf("%d\n", sdslen(s)); + * + * The output will be "2", but if we comment out the call to sdsupdatelen() + * the output will be "6" as the string was modified but the logical length + * remains 6 bytes. */ +void sdsupdatelen(sds s) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + int reallen = strlen(s); + sh->free += (sh->len-reallen); + sh->len = reallen; +} + +/* Modify an sds string on-place to make it empty (zero length). + * However all the existing buffer is not discarded but set as free space + * so that next append operations will not require allocations up to the + * number of bytes previously available. */ +void sdsclear(sds s) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + sh->free += sh->len; + sh->len = 0; + sh->buf[0] = '\0'; +} + +/* Enlarge the free space at the end of the sds string so that the caller + * is sure that after calling this function can overwrite up to addlen + * bytes after the end of the string, plus one more byte for nul term. + * + * Note: this does not change the *length* of the sds string as returned + * by sdslen(), but only the free buffer space we have. */ +sds sdsMakeRoomFor(sds s, size_t addlen) { + struct sdshdr *sh, *newsh; + size_t free = sdsavail(s); + size_t len, newlen; + + if (free >= addlen) return s; + len = sdslen(s); + sh = (void*) (s-sizeof *sh); + newlen = (len+addlen); + if (newlen < SDS_MAX_PREALLOC) + newlen *= 2; + else + newlen += SDS_MAX_PREALLOC; + newsh = realloc(sh, sizeof *newsh+newlen+1); + if (newsh == NULL) return NULL; + + newsh->free = newlen - len; + return newsh->buf; +} + +/* Reallocate the sds string so that it has no free space at the end. The + * contained string remains not altered, but next concatenation operations + * will require a reallocation. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdsRemoveFreeSpace(sds s) { + struct sdshdr *sh; + + sh = (void*) (s-sizeof *sh); + sh = realloc(sh, sizeof *sh+sh->len+1); + sh->free = 0; + return sh->buf; +} + +/* Return the total size of the allocation of the specifed sds string, + * including: + * 1) The sds header before the pointer. + * 2) The string. + * 3) The free buffer at the end if any. + * 4) The implicit null term. + */ +size_t sdsAllocSize(sds s) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + + return sizeof(*sh)+sh->len+sh->free+1; +} + +/* Increment the sds length and decrements the left free space at the + * end of the string according to 'incr'. Also set the null term + * in the new end of the string. + * + * This function is used in order to fix the string length after the + * user calls sdsMakeRoomFor(), writes something after the end of + * the current string, and finally needs to set the new length. + * + * Note: it is possible to use a negative increment in order to + * right-trim the string. + * + * Usage example: + * + * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the + * following schema, to cat bytes coming from the kernel to the end of an + * sds string without copying into an intermediate buffer: + * + * oldlen = sdslen(s); + * s = sdsMakeRoomFor(s, BUFFER_SIZE); + * nread = read(fd, s+oldlen, BUFFER_SIZE); + * ... check for nread <= 0 and handle it ... + * sdsIncrLen(s, nread); + */ +void sdsIncrLen(sds s, int incr) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + + assert(sh->free >= incr); + sh->len += incr; + sh->free -= incr; + assert(sh->free >= 0); + s[sh->len] = '\0'; +} + +/* Grow the sds to have the specified length. Bytes that were not part of + * the original length of the sds will be set to zero. + * + * if the specified length is smaller than the current length, no operation + * is performed. */ +sds sdsgrowzero(sds s, size_t len) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + size_t totlen, curlen = sh->len; + + if (len <= curlen) return s; + s = sdsMakeRoomFor(s,len-curlen); + if (s == NULL) return NULL; + + /* Make sure added region doesn't contain garbage */ + sh = (void*)(s-sizeof *sh); + memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ + totlen = sh->len+sh->free; + sh->len = len; + sh->free = totlen-sh->len; + return s; +} + +/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the + * end of the specified sds string 's'. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatlen(sds s, const void *t, size_t len) { + struct sdshdr *sh; + size_t curlen = sdslen(s); + + s = sdsMakeRoomFor(s,len); + if (s == NULL) return NULL; + sh = (void*) (s-sizeof *sh); + memcpy(s+curlen, t, len); + sh->len = curlen+len; + sh->free = sh->free-len; + s[curlen+len] = '\0'; + return s; +} + +/* Append the specified null termianted C string to the sds string 's'. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscat(sds s, const char *t) { + return sdscatlen(s, t, strlen(t)); +} + +/* Append the specified sds 't' to the existing sds 's'. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatsds(sds s, const sds t) { + return sdscatlen(s, t, sdslen(t)); +} + +/* Destructively modify the sds string 's' to hold the specified binary + * safe string pointed by 't' of length 'len' bytes. */ +sds sdscpylen(sds s, const char *t, size_t len) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + size_t totlen = sh->free+sh->len; + + if (totlen < len) { + s = sdsMakeRoomFor(s,len-sh->len); + if (s == NULL) return NULL; + sh = (void*) (s-sizeof *sh); + totlen = sh->free+sh->len; + } + memcpy(s, t, len); + s[len] = '\0'; + sh->len = len; + sh->free = totlen-len; + return s; +} + +/* Like sdscpylen() but 't' must be a null-termined string so that the length + * of the string is obtained with strlen(). */ +sds sdscpy(sds s, const char *t) { + return sdscpylen(s, t, strlen(t)); +} + +/* Helper for sdscatlonglong() doing the actual number -> string + * conversion. 's' must point to a string with room for at least + * SDS_LLSTR_SIZE bytes. + * + * The function returns the lenght of the null-terminated string + * representation stored at 's'. */ +#define SDS_LLSTR_SIZE 21 +int sdsll2str(char *s, long long value) { + char *p, aux; + unsigned long long v; + size_t l; + + /* Generate the string representation, this method produces + * an reversed string. */ + v = (value < 0) ? -value : value; + p = s; + do { + *p++ = '0'+(v%10); + v /= 10; + } while(v); + if (value < 0) *p++ = '-'; + + /* Compute length and add null term. */ + l = p-s; + *p = '\0'; + + /* Reverse the string. */ + p--; + while(s < p) { + aux = *s; + *s = *p; + *p = aux; + s++; + p--; + } + return l; +} + +/* Identical sdsll2str(), but for unsigned long long type. */ +int sdsull2str(char *s, unsigned long long v) { + char *p, aux; + size_t l; + + /* Generate the string representation, this method produces + * an reversed string. */ + p = s; + do { + *p++ = '0'+(v%10); + v /= 10; + } while(v); + + /* Compute length and add null term. */ + l = p-s; + *p = '\0'; + + /* Reverse the string. */ + p--; + while(s < p) { + aux = *s; + *s = *p; + *p = aux; + s++; + p--; + } + return l; +} + +/* Like sdscatpritf() but gets va_list instead of being variadic. */ +sds sdscatvprintf(sds s, const char *fmt, va_list ap) { + va_list cpy; + char *buf, *t; + size_t buflen = 16; + + while(1) { + buf = malloc(buflen); + if (buf == NULL) return NULL; + buf[buflen-2] = '\0'; + va_copy(cpy,ap); + vsnprintf(buf, buflen, fmt, cpy); + if (buf[buflen-2] != '\0') { + free(buf); + buflen *= 2; + continue; + } + break; + } + t = sdscat(s, buf); + free(buf); + return t; +} + +/* Append to the sds string 's' a string obtained using printf-alike format + * specifier. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. + * + * Example: + * + * s = sdsnew("Sum is: "); + * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b); + * + * Often you need to create a string from scratch with the printf-alike + * format. When this is the need, just use sdsempty() as the target string: + * + * s = sdscatprintf(sdsempty(), "... your format ...", args); + */ +sds sdscatprintf(sds s, const char *fmt, ...) { + va_list ap; + char *t; + va_start(ap, fmt); + t = sdscatvprintf(s,fmt,ap); + va_end(ap); + return t; +} + +/* This function is similar to sdscatprintf, but much faster as it does + * not rely on sprintf() family functions implemented by the libc that + * are often very slow. Moreover directly handling the sds string as + * new data is concatenated provides a performance improvement. + * + * However this function only handles an incompatible subset of printf-alike + * format specifiers: + * + * %s - C String + * %S - SDS string + * %i - signed int + * %I - 64 bit signed integer (long long, int64_t) + * %u - unsigned int + * %U - 64 bit unsigned integer (unsigned long long, uint64_t) + * %T - A size_t variable. + * %% - Verbatim "%" character. + */ +sds sdscatfmt(sds s, char const *fmt, ...) { + struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); + size_t initlen = sdslen(s); + const char *f = fmt; + int i; + va_list ap; + + va_start(ap,fmt); + f = fmt; /* Next format specifier byte to process. */ + i = initlen; /* Position of the next byte to write to dest str. */ + while(*f) { + char next, *str; + int l; + long long num; + unsigned long long unum; + + /* Make sure there is always space for at least 1 char. */ + if (sh->free == 0) { + s = sdsMakeRoomFor(s,1); + sh = (void*) (s-(sizeof(struct sdshdr))); + } + + switch(*f) { + case '%': + next = *(f+1); + f++; + switch(next) { + case 's': + case 'S': + str = va_arg(ap,char*); + l = (next == 's') ? strlen(str) : sdslen(str); + if (sh->free < l) { + s = sdsMakeRoomFor(s,l); + sh = (void*) (s-(sizeof(struct sdshdr))); + } + memcpy(s+i,str,l); + sh->len += l; + sh->free -= l; + i += l; + break; + case 'i': + case 'I': + if (next == 'i') + num = va_arg(ap,int); + else + num = va_arg(ap,long long); + { + char buf[SDS_LLSTR_SIZE]; + l = sdsll2str(buf,num); + if (sh->free < l) { + s = sdsMakeRoomFor(s,l); + sh = (void*) (s-(sizeof(struct sdshdr))); + } + memcpy(s+i,buf,l); + sh->len += l; + sh->free -= l; + i += l; + } + break; + case 'u': + case 'U': + case 'T': + if (next == 'u') + unum = va_arg(ap,unsigned int); + else if(next == 'U') + unum = va_arg(ap,unsigned long long); + else + unum = (unsigned long long)va_arg(ap,size_t); + { + char buf[SDS_LLSTR_SIZE]; + l = sdsull2str(buf,unum); + if (sh->free < l) { + s = sdsMakeRoomFor(s,l); + sh = (void*) (s-(sizeof(struct sdshdr))); + } + memcpy(s+i,buf,l); + sh->len += l; + sh->free -= l; + i += l; + } + break; + default: /* Handle %% and generally %. */ + s[i++] = next; + sh->len += 1; + sh->free -= 1; + break; + } + break; + default: + s[i++] = *f; + sh->len += 1; + sh->free -= 1; + break; + } + f++; + } + va_end(ap); + + /* Add null-term */ + s[i] = '\0'; + return s; +} + + +/* Remove the part of the string from left and from right composed just of + * contiguous characters found in 'cset', that is a null terminted C string. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. + * + * Example: + * + * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); + * s = sdstrim(s,"A. :"); + * printf("%s\n", s); + * + * Output will be just "Hello World". + */ +void sdstrim(sds s, const char *cset) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + char *start, *end, *sp, *ep; + size_t len; + + sp = start = s; + ep = end = s+sdslen(s)-1; + while(sp <= end && strchr(cset, *sp)) sp++; + while(ep > start && strchr(cset, *ep)) ep--; + len = (sp > ep) ? 0 : ((ep-sp)+1); + if (sh->buf != sp) memmove(sh->buf, sp, len); + sh->buf[len] = '\0'; + sh->free = sh->free+(sh->len-len); + sh->len = len; +} + +/* Turn the string into a smaller (or equal) string containing only the + * substring specified by the 'start' and 'end' indexes. + * + * start and end can be negative, where -1 means the last character of the + * string, -2 the penultimate character, and so forth. + * + * The interval is inclusive, so the start and end characters will be part + * of the resulting string. + * + * The string is modified in-place. + * + * Example: + * + * s = sdsnew("Hello World"); + * sdsrange(s,1,-1); => "ello World" + */ +void sdsrange(sds s, int start, int end) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + size_t newlen, len = sdslen(s); + + if (len == 0) return; + if (start < 0) { + start = len+start; + if (start < 0) start = 0; + } + if (end < 0) { + end = len+end; + if (end < 0) end = 0; + } + newlen = (start > end) ? 0 : (end-start)+1; + if (newlen != 0) { + if (start >= (signed)len) { + newlen = 0; + } else if (end >= (signed)len) { + end = len-1; + newlen = (start > end) ? 0 : (end-start)+1; + } + } else { + start = 0; + } + if (start && newlen) memmove(sh->buf, sh->buf+start, newlen); + sh->buf[newlen] = 0; + sh->free = sh->free+(sh->len-newlen); + sh->len = newlen; +} + +/* Apply tolower() to every character of the sds string 's'. */ +void sdstolower(sds s) { + int len = sdslen(s), j; + + for (j = 0; j < len; j++) s[j] = tolower(s[j]); +} + +/* Apply toupper() to every character of the sds string 's'. */ +void sdstoupper(sds s) { + int len = sdslen(s), j; + + for (j = 0; j < len; j++) s[j] = toupper(s[j]); +} + +/* Compare two sds strings s1 and s2 with memcmp(). + * + * Return value: + * + * 1 if s1 > s2. + * -1 if s1 < s2. + * 0 if s1 and s2 are exactly the same binary string. + * + * If two strings share exactly the same prefix, but one of the two has + * additional characters, the longer string is considered to be greater than + * the smaller one. */ +int sdscmp(const sds s1, const sds s2) { + size_t l1, l2, minlen; + int cmp; + + l1 = sdslen(s1); + l2 = sdslen(s2); + minlen = (l1 < l2) ? l1 : l2; + cmp = memcmp(s1,s2,minlen); + if (cmp == 0) return l1-l2; + return cmp; +} + +/* Split 's' with separator in 'sep'. An array + * of sds strings is returned. *count will be set + * by reference to the number of tokens returned. + * + * On out of memory, zero length string, zero length + * separator, NULL is returned. + * + * Note that 'sep' is able to split a string using + * a multi-character separator. For example + * sdssplit("foo_-_bar","_-_"); will return two + * elements "foo" and "bar". + * + * This version of the function is binary-safe but + * requires length arguments. sdssplit() is just the + * same function but for zero-terminated strings. + */ +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { + int elements = 0, slots = 5, start = 0, j; + sds *tokens; + + if (seplen < 1 || len < 0) return NULL; + + tokens = malloc(sizeof(sds)*slots); + if (tokens == NULL) return NULL; + + if (len == 0) { + *count = 0; + return tokens; + } + for (j = 0; j < (len-(seplen-1)); j++) { + /* make sure there is room for the next element and the final one */ + if (slots < elements+2) { + sds *newtokens; + + slots *= 2; + newtokens = realloc(tokens,sizeof(sds)*slots); + if (newtokens == NULL) goto cleanup; + tokens = newtokens; + } + /* search the separator */ + if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { + tokens[elements] = sdsnewlen(s+start,j-start); + if (tokens[elements] == NULL) goto cleanup; + elements++; + start = j+seplen; + j = j+seplen-1; /* skip the separator */ + } + } + /* Add the final element. We are sure there is room in the tokens array. */ + tokens[elements] = sdsnewlen(s+start,len-start); + if (tokens[elements] == NULL) goto cleanup; + elements++; + *count = elements; + return tokens; + +cleanup: + { + int i; + for (i = 0; i < elements; i++) sdsfree(tokens[i]); + free(tokens); + *count = 0; + return NULL; + } +} + +/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ +void sdsfreesplitres(sds *tokens, int count) { + if (!tokens) return; + while(count--) + sdsfree(tokens[count]); + free(tokens); +} + +/* Create an sds string from a long long value. It is much faster than: + * + * sdscatprintf(sdsempty(),"%lld\n", value); + */ +sds sdsfromlonglong(long long value) { + char buf[32], *p; + unsigned long long v; + + v = (value < 0) ? -value : value; + p = buf+31; /* point to the last character */ + do { + *p-- = '0'+(v%10); + v /= 10; + } while(v); + if (value < 0) *p-- = '-'; + p++; + return sdsnewlen(p,32-(p-buf)); +} + +/* Append to the sds string "s" an escaped string representation where + * all the non-printable characters (tested with isprint()) are turned into + * escapes in the form "\n\r\a...." or "\x". + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatrepr(sds s, const char *p, size_t len) { + s = sdscatlen(s,"\"",1); + while(len--) { + switch(*p) { + case '\\': + case '"': + s = sdscatprintf(s,"\\%c",*p); + break; + case '\n': s = sdscatlen(s,"\\n",2); break; + case '\r': s = sdscatlen(s,"\\r",2); break; + case '\t': s = sdscatlen(s,"\\t",2); break; + case '\a': s = sdscatlen(s,"\\a",2); break; + case '\b': s = sdscatlen(s,"\\b",2); break; + default: + if (isprint(*p)) + s = sdscatprintf(s,"%c",*p); + else + s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); + break; + } + p++; + } + return sdscatlen(s,"\"",1); +} + +/* Helper function for sdssplitargs() that returns non zero if 'c' + * is a valid hex digit. */ +int is_hex_digit(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); +} + +/* Helper function for sdssplitargs() that converts a hex digit into an + * integer from 0 to 15 */ +int hex_digit_to_int(char c) { + switch(c) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + default: return 0; + } +} + +/* Split a line into arguments, where every argument can be in the + * following programming-language REPL-alike form: + * + * foo bar "newline are supported\n" and "\xff\x00otherstuff" + * + * The number of arguments is stored into *argc, and an array + * of sds is returned. + * + * The caller should free the resulting array of sds strings with + * sdsfreesplitres(). + * + * Note that sdscatrepr() is able to convert back a string into + * a quoted string in the same format sdssplitargs() is able to parse. + * + * The function returns the allocated tokens on success, even when the + * input string is empty, or NULL if the input contains unbalanced + * quotes or closed quotes followed by non space characters + * as in: "foo"bar or "foo' + */ +sds *sdssplitargs(const char *line, int *argc) { + const char *p = line; + char *current = NULL; + char **vector = NULL; + + *argc = 0; + while(1) { + /* skip blanks */ + while(*p && isspace(*p)) p++; + if (*p) { + /* get a token */ + int inq=0; /* set to 1 if we are in "quotes" */ + int insq=0; /* set to 1 if we are in 'single quotes' */ + int done=0; + + if (current == NULL) current = sdsempty(); + while(!done) { + if (inq) { + if (*p == '\\' && *(p+1) == 'x' && + is_hex_digit(*(p+2)) && + is_hex_digit(*(p+3))) + { + unsigned char byte; + + byte = (hex_digit_to_int(*(p+2))*16)+ + hex_digit_to_int(*(p+3)); + current = sdscatlen(current,(char*)&byte,1); + p += 3; + } else if (*p == '\\' && *(p+1)) { + char c; + + p++; + switch(*p) { + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'b': c = '\b'; break; + case 'a': c = '\a'; break; + default: c = *p; break; + } + current = sdscatlen(current,&c,1); + } else if (*p == '"') { + /* closing quote must be followed by a space or + * nothing at all. */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; + } else { + current = sdscatlen(current,p,1); + } + } else if (insq) { + if (*p == '\\' && *(p+1) == '\'') { + p++; + current = sdscatlen(current,"'",1); + } else if (*p == '\'') { + /* closing quote must be followed by a space or + * nothing at all. */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; + } else { + current = sdscatlen(current,p,1); + } + } else { + switch(*p) { + case ' ': + case '\n': + case '\r': + case '\t': + case '\0': + done=1; + break; + case '"': + inq=1; + break; + case '\'': + insq=1; + break; + default: + current = sdscatlen(current,p,1); + break; + } + } + if (*p) p++; + } + /* add the token to the vector */ + vector = realloc(vector,((*argc)+1)*sizeof(char*)); + vector[*argc] = current; + (*argc)++; + current = NULL; + } else { + /* Even on empty input string return something not NULL. */ + if (vector == NULL) vector = malloc(sizeof(void*)); + return vector; + } + } + +err: + while((*argc)--) + sdsfree(vector[*argc]); + free(vector); + if (current) sdsfree(current); + *argc = 0; + return NULL; +} + +/* Modify the string substituting all the occurrences of the set of + * characters specified in the 'from' string to the corresponding character + * in the 'to' array. + * + * For instance: sdsmapchars(mystring, "ho", "01", 2) + * will have the effect of turning the string "hello" into "0ell1". + * + * The function returns the sds string pointer, that is always the same + * as the input pointer since no resize is needed. */ +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { + size_t j, i, l = sdslen(s); + + for (j = 0; j < l; j++) { + for (i = 0; i < setlen; i++) { + if (s[j] == from[i]) { + s[j] = to[i]; + break; + } + } + } + return s; +} + +/* Join an array of C strings using the specified separator (also a C string). + * Returns the result as an sds string. */ +sds sdsjoin(char **argv, int argc, char *sep, size_t seplen) { + sds join = sdsempty(); + int j; + + for (j = 0; j < argc; j++) { + join = sdscat(join, argv[j]); + if (j != argc-1) join = sdscatlen(join,sep,seplen); + } + return join; +} + +/* Like sdsjoin, but joins an array of SDS strings. */ +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { + sds join = sdsempty(); + int j; + + for (j = 0; j < argc; j++) { + join = sdscatsds(join, argv[j]); + if (j != argc-1) join = sdscatlen(join,sep,seplen); + } + return join; +} + +#ifdef SDS_TEST_MAIN +#include +#include "testhelp.h" + +int main(void) { + { + struct sdshdr *sh; + sds x = sdsnew("foo"), y; + + test_cond("Create a string and obtain the length", + sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) + + sdsfree(x); + x = sdsnewlen("foo",2); + test_cond("Create a string with specified length", + sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) + + x = sdscat(x,"bar"); + test_cond("Strings concatenation", + sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); + + x = sdscpy(x,"a"); + test_cond("sdscpy() against an originally longer string", + sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) + + x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); + test_cond("sdscpy() against an originally shorter string", + sdslen(x) == 33 && + memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) + + sdsfree(x); + x = sdscatprintf(sdsempty(),"%d",123); + test_cond("sdscatprintf() seems working in the base case", + sdslen(x) == 3 && memcmp(x,"123\0",4) ==0) + + sdsfree(x); + x = sdsnew("xxciaoyyy"); + sdstrim(x,"xy"); + test_cond("sdstrim() correctly trims characters", + sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) + + y = sdsdup(x); + sdsrange(y,1,1); + test_cond("sdsrange(...,1,1)", + sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,1,-1); + test_cond("sdsrange(...,1,-1)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,-2,-1); + test_cond("sdsrange(...,-2,-1)", + sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,2,1); + test_cond("sdsrange(...,2,1)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,1,100); + test_cond("sdsrange(...,1,100)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,100,100); + test_cond("sdsrange(...,100,100)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("foo"); + y = sdsnew("foa"); + test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("bar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("aar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) + + sdsfree(y); + sdsfree(x); + x = sdsnewlen("\a\n\0foo\r",7); + y = sdscatrepr(sdsempty(),x,sdslen(x)); + test_cond("sdscatrepr(...data...)", + memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) + + { + int oldfree; + + sdsfree(x); + x = sdsnew("0"); + sh = (void*) (x-(sizeof(struct sdshdr))); + test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0); + x = sdsMakeRoomFor(x,1); + sh = (void*) (x-(sizeof(struct sdshdr))); + test_cond("sdsMakeRoomFor()", sh->len == 1 && sh->free > 0); + oldfree = sh->free; + x[1] = '1'; + sdsIncrLen(x,1); + test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1'); + test_cond("sdsIncrLen() -- len", sh->len == 2); + test_cond("sdsIncrLen() -- free", sh->free == oldfree-1); + } + } + test_report() + return 0; +} +#endif diff --git a/ext/hiredis-vip-0.3.0/sds.h b/ext/hiredis-vip-0.3.0/sds.h new file mode 100644 index 000000000..19a2abd31 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/sds.h @@ -0,0 +1,105 @@ +/* SDS (Simple Dynamic Strings), A C dynamic strings library. + * + * Copyright (c) 2006-2014, Salvatore Sanfilippo + * 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. + */ + +#ifndef __SDS_H +#define __SDS_H + +#define SDS_MAX_PREALLOC (1024*1024) + +#include +#include +#ifdef _MSC_VER +#include "win32.h" +#endif + +typedef char *sds; + +struct sdshdr { + int len; + int free; + char buf[]; +}; + +static inline size_t sdslen(const sds s) { + struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh); + return sh->len; +} + +static inline size_t sdsavail(const sds s) { + struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh); + return sh->free; +} + +sds sdsnewlen(const void *init, size_t initlen); +sds sdsnew(const char *init); +sds sdsempty(void); +size_t sdslen(const sds s); +sds sdsdup(const sds s); +void sdsfree(sds s); +size_t sdsavail(const sds s); +sds sdsgrowzero(sds s, size_t len); +sds sdscatlen(sds s, const void *t, size_t len); +sds sdscat(sds s, const char *t); +sds sdscatsds(sds s, const sds t); +sds sdscpylen(sds s, const char *t, size_t len); +sds sdscpy(sds s, const char *t); + +sds sdscatvprintf(sds s, const char *fmt, va_list ap); +#ifdef __GNUC__ +sds sdscatprintf(sds s, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +#else +sds sdscatprintf(sds s, const char *fmt, ...); +#endif + +sds sdscatfmt(sds s, char const *fmt, ...); +void sdstrim(sds s, const char *cset); +void sdsrange(sds s, int start, int end); +void sdsupdatelen(sds s); +void sdsclear(sds s); +int sdscmp(const sds s1, const sds s2); +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); +void sdsfreesplitres(sds *tokens, int count); +void sdstolower(sds s); +void sdstoupper(sds s); +sds sdsfromlonglong(long long value); +sds sdscatrepr(sds s, const char *p, size_t len); +sds *sdssplitargs(const char *line, int *argc); +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); +sds sdsjoin(char **argv, int argc, char *sep, size_t seplen); +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); + +/* Low level functions exposed to the user API */ +sds sdsMakeRoomFor(sds s, size_t addlen); +void sdsIncrLen(sds s, int incr); +sds sdsRemoveFreeSpace(sds s); +size_t sdsAllocSize(sds s); + +#endif diff --git a/ext/hiredis-vip-0.3.0/test.c b/ext/hiredis-vip-0.3.0/test.c new file mode 100644 index 000000000..8fde55446 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/test.c @@ -0,0 +1,806 @@ +#include "fmacros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hiredis.h" +#include "net.h" + +enum connection_type { + CONN_TCP, + CONN_UNIX, + CONN_FD +}; + +struct config { + enum connection_type type; + + struct { + const char *host; + int port; + struct timeval timeout; + } tcp; + + struct { + const char *path; + } unix; +}; + +/* The following lines make up our testing "framework" :) */ +static int tests = 0, fails = 0; +#define test(_s) { printf("#%02d ", ++tests); printf(_s); } +#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} + +static long long usec(void) { + struct timeval tv; + gettimeofday(&tv,NULL); + return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; +} + +/* The assert() calls below have side effects, so we need assert() + * even if we are compiling without asserts (-DNDEBUG). */ +#ifdef NDEBUG +#undef assert +#define assert(e) (void)(e) +#endif + +static redisContext *select_database(redisContext *c) { + redisReply *reply; + + /* Switch to DB 9 for testing, now that we know we can chat. */ + reply = redisCommand(c,"SELECT 9"); + assert(reply != NULL); + freeReplyObject(reply); + + /* Make sure the DB is emtpy */ + reply = redisCommand(c,"DBSIZE"); + assert(reply != NULL); + if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) { + /* Awesome, DB 9 is empty and we can continue. */ + freeReplyObject(reply); + } else { + printf("Database #9 is not empty, test can not continue\n"); + exit(1); + } + + return c; +} + +static int disconnect(redisContext *c, int keep_fd) { + redisReply *reply; + + /* Make sure we're on DB 9. */ + reply = redisCommand(c,"SELECT 9"); + assert(reply != NULL); + freeReplyObject(reply); + reply = redisCommand(c,"FLUSHDB"); + assert(reply != NULL); + freeReplyObject(reply); + + /* Free the context as well, but keep the fd if requested. */ + if (keep_fd) + return redisFreeKeepFd(c); + redisFree(c); + return -1; +} + +static redisContext *connect(struct config config) { + redisContext *c = NULL; + + if (config.type == CONN_TCP) { + c = redisConnect(config.tcp.host, config.tcp.port); + } else if (config.type == CONN_UNIX) { + c = redisConnectUnix(config.unix.path); + } else if (config.type == CONN_FD) { + /* Create a dummy connection just to get an fd to inherit */ + redisContext *dummy_ctx = redisConnectUnix(config.unix.path); + if (dummy_ctx) { + int fd = disconnect(dummy_ctx, 1); + printf("Connecting to inherited fd %d\n", fd); + c = redisConnectFd(fd); + } + } else { + assert(NULL); + } + + if (c == NULL) { + printf("Connection error: can't allocate redis context\n"); + exit(1); + } else if (c->err) { + printf("Connection error: %s\n", c->errstr); + redisFree(c); + exit(1); + } + + return select_database(c); +} + +static void test_format_commands(void) { + char *cmd; + int len; + + test("Format command without interpolation: "); + len = redisFormatCommand(&cmd,"SET foo bar"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%s string interpolation: "); + len = redisFormatCommand(&cmd,"SET %s %s","foo","bar"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%s and an empty string: "); + len = redisFormatCommand(&cmd,"SET %s %s","foo",""); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(0+2)); + free(cmd); + + test("Format command with an empty string in between proper interpolations: "); + len = redisFormatCommand(&cmd,"SET %s %s","","foo"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 && + len == 4+4+(3+2)+4+(0+2)+4+(3+2)); + free(cmd); + + test("Format command with %%b string interpolation: "); + len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%b and an empty string: "); + len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(0+2)); + free(cmd); + + test("Format command with literal %%: "); + len = redisFormatCommand(&cmd,"SET %% %%"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 && + len == 4+4+(3+2)+4+(1+2)+4+(1+2)); + free(cmd); + + /* Vararg width depends on the type. These tests make sure that the + * width is correctly determined using the format and subsequent varargs + * can correctly be interpolated. */ +#define INTEGER_WIDTH_TEST(fmt, type) do { \ + type value = 123; \ + test("Format command with printf-delegation (" #type "): "); \ + len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \ + test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \ + len == 4+5+(12+2)+4+(9+2)); \ + free(cmd); \ +} while(0) + +#define FLOAT_WIDTH_TEST(type) do { \ + type value = 123.0; \ + test("Format command with printf-delegation (" #type "): "); \ + len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \ + test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \ + len == 4+5+(12+2)+4+(9+2)); \ + free(cmd); \ +} while(0) + + INTEGER_WIDTH_TEST("d", int); + INTEGER_WIDTH_TEST("hhd", char); + INTEGER_WIDTH_TEST("hd", short); + INTEGER_WIDTH_TEST("ld", long); + INTEGER_WIDTH_TEST("lld", long long); + INTEGER_WIDTH_TEST("u", unsigned int); + INTEGER_WIDTH_TEST("hhu", unsigned char); + INTEGER_WIDTH_TEST("hu", unsigned short); + INTEGER_WIDTH_TEST("lu", unsigned long); + INTEGER_WIDTH_TEST("llu", unsigned long long); + FLOAT_WIDTH_TEST(float); + FLOAT_WIDTH_TEST(double); + + test("Format command with invalid printf format: "); + len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); + test_cond(len == -1); + + const char *argv[3]; + argv[0] = "SET"; + argv[1] = "foo\0xxx"; + argv[2] = "bar"; + size_t lens[3] = { 3, 7, 3 }; + int argc = 3; + + test("Format command by passing argc/argv without lengths: "); + len = redisFormatCommandArgv(&cmd,argc,argv,NULL); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command by passing argc/argv with lengths: "); + len = redisFormatCommandArgv(&cmd,argc,argv,lens); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(7+2)+4+(3+2)); + free(cmd); +} + +static void test_append_formatted_commands(struct config config) { + redisContext *c; + redisReply *reply; + char *cmd; + int len; + + c = connect(config); + + test("Append format command: "); + + len = redisFormatCommand(&cmd, "SET foo bar"); + + test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK); + + assert(redisGetReply(c, (void*)&reply) == REDIS_OK); + + free(cmd); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_reply_reader(void) { + redisReader *reader; + void *reply; + int ret; + int i; + + test("Error handling in reply parser: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"@foo\r\n",6); + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); + redisReaderFree(reader); + + /* when the reply already contains multiple items, they must be free'd + * on an error. valgrind will bark when this doesn't happen. */ + test("Memory cleanup in reply parser: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*2\r\n",4); + redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); + redisReaderFeed(reader,(char*)"@foo\r\n",6); + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); + redisReaderFree(reader); + + test("Set error on nested multi bulks with depth > 7: "); + reader = redisReaderCreate(); + + for (i = 0; i < 9; i++) { + redisReaderFeed(reader,(char*)"*1\r\n",4); + } + + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strncasecmp(reader->errstr,"No support for",14) == 0); + redisReaderFree(reader); + + test("Works with NULL functions for reply: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"+OK\r\n",5); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); + redisReaderFree(reader); + + test("Works when a single newline (\\r\\n) covers two calls to feed: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"+OK\r",4); + ret = redisReaderGetReply(reader,&reply); + assert(ret == REDIS_OK && reply == NULL); + redisReaderFeed(reader,(char*)"\n",1); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); + redisReaderFree(reader); + + test("Don't reset state after protocol error: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"x",1); + ret = redisReaderGetReply(reader,&reply); + assert(ret == REDIS_ERR); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && reply == NULL); + redisReaderFree(reader); + + /* Regression test for issue #45 on GitHub. */ + test("Don't do empty allocation for empty multi bulk: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*0\r\n",4); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && + ((redisReply*)reply)->elements == 0); + freeReplyObject(reply); + redisReaderFree(reader); +} + +static void test_free_null(void) { + void *redisContext = NULL; + void *reply = NULL; + + test("Don't fail when redisFree is passed a NULL value: "); + redisFree(redisContext); + test_cond(redisContext == NULL); + + test("Don't fail when freeReplyObject is passed a NULL value: "); + freeReplyObject(reply); + test_cond(reply == NULL); +} + +static void test_blocking_connection_errors(void) { + redisContext *c; + + test("Returns error when host cannot be resolved: "); + c = redisConnect((char*)"idontexist.test", 6379); + test_cond(c->err == REDIS_ERR_OTHER && + (strcmp(c->errstr,"Name or service not known") == 0 || + strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 || + strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || + strcmp(c->errstr,"No address associated with hostname") == 0 || + strcmp(c->errstr,"Temporary failure in name resolution") == 0 || + strcmp(c->errstr,"no address associated with name") == 0)); + redisFree(c); + + test("Returns error when the port is not open: "); + c = redisConnect((char*)"localhost", 1); + test_cond(c->err == REDIS_ERR_IO && + strcmp(c->errstr,"Connection refused") == 0); + redisFree(c); + + test("Returns error when the unix socket path doesn't accept connections: "); + c = redisConnectUnix((char*)"/tmp/idontexist.sock"); + test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ + redisFree(c); +} + +static void test_blocking_connection(struct config config) { + redisContext *c; + redisReply *reply; + + c = connect(config); + + test("Is able to deliver commands: "); + reply = redisCommand(c,"PING"); + test_cond(reply->type == REDIS_REPLY_STATUS && + strcasecmp(reply->str,"pong") == 0) + freeReplyObject(reply); + + test("Is a able to send commands verbatim: "); + reply = redisCommand(c,"SET foo bar"); + test_cond (reply->type == REDIS_REPLY_STATUS && + strcasecmp(reply->str,"ok") == 0) + freeReplyObject(reply); + + test("%%s String interpolation works: "); + reply = redisCommand(c,"SET %s %s","foo","hello world"); + freeReplyObject(reply); + reply = redisCommand(c,"GET foo"); + test_cond(reply->type == REDIS_REPLY_STRING && + strcmp(reply->str,"hello world") == 0); + freeReplyObject(reply); + + test("%%b String interpolation works: "); + reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11); + freeReplyObject(reply); + reply = redisCommand(c,"GET foo"); + test_cond(reply->type == REDIS_REPLY_STRING && + memcmp(reply->str,"hello\x00world",11) == 0) + + test("Binary reply length is correct: "); + test_cond(reply->len == 11) + freeReplyObject(reply); + + test("Can parse nil replies: "); + reply = redisCommand(c,"GET nokey"); + test_cond(reply->type == REDIS_REPLY_NIL) + freeReplyObject(reply); + + /* test 7 */ + test("Can parse integer replies: "); + reply = redisCommand(c,"INCR mycounter"); + test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) + freeReplyObject(reply); + + test("Can parse multi bulk replies: "); + freeReplyObject(redisCommand(c,"LPUSH mylist foo")); + freeReplyObject(redisCommand(c,"LPUSH mylist bar")); + reply = redisCommand(c,"LRANGE mylist 0 -1"); + test_cond(reply->type == REDIS_REPLY_ARRAY && + reply->elements == 2 && + !memcmp(reply->element[0]->str,"bar",3) && + !memcmp(reply->element[1]->str,"foo",3)) + freeReplyObject(reply); + + /* m/e with multi bulk reply *before* other reply. + * specifically test ordering of reply items to parse. */ + test("Can handle nested multi bulk replies: "); + freeReplyObject(redisCommand(c,"MULTI")); + freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1")); + freeReplyObject(redisCommand(c,"PING")); + reply = (redisCommand(c,"EXEC")); + test_cond(reply->type == REDIS_REPLY_ARRAY && + reply->elements == 2 && + reply->element[0]->type == REDIS_REPLY_ARRAY && + reply->element[0]->elements == 2 && + !memcmp(reply->element[0]->element[0]->str,"bar",3) && + !memcmp(reply->element[0]->element[1]->str,"foo",3) && + reply->element[1]->type == REDIS_REPLY_STATUS && + strcasecmp(reply->element[1]->str,"pong") == 0); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_blocking_connection_timeouts(struct config config) { + redisContext *c; + redisReply *reply; + ssize_t s; + const char *cmd = "DEBUG SLEEP 3\r\n"; + struct timeval tv; + + c = connect(config); + test("Successfully completes a command when the timeout is not exceeded: "); + reply = redisCommand(c,"SET foo fast"); + freeReplyObject(reply); + tv.tv_sec = 0; + tv.tv_usec = 10000; + redisSetTimeout(c, tv); + reply = redisCommand(c, "GET foo"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0); + freeReplyObject(reply); + disconnect(c, 0); + + c = connect(config); + test("Does not return a reply when the command times out: "); + s = write(c->fd, cmd, strlen(cmd)); + tv.tv_sec = 0; + tv.tv_usec = 10000; + redisSetTimeout(c, tv); + reply = redisCommand(c, "GET foo"); + test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0); + freeReplyObject(reply); + + test("Reconnect properly reconnects after a timeout: "); + redisReconnect(c); + reply = redisCommand(c, "PING"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); + freeReplyObject(reply); + + test("Reconnect properly uses owned parameters: "); + config.tcp.host = "foo"; + config.unix.path = "foo"; + redisReconnect(c); + reply = redisCommand(c, "PING"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_blocking_io_errors(struct config config) { + redisContext *c; + redisReply *reply; + void *_reply; + int major, minor; + + /* Connect to target given by config. */ + c = connect(config); + { + /* Find out Redis version to determine the path for the next test */ + const char *field = "redis_version:"; + char *p, *eptr; + + reply = redisCommand(c,"INFO"); + p = strstr(reply->str,field); + major = strtol(p+strlen(field),&eptr,10); + p = eptr+1; /* char next to the first "." */ + minor = strtol(p,&eptr,10); + freeReplyObject(reply); + } + + test("Returns I/O error when the connection is lost: "); + reply = redisCommand(c,"QUIT"); + if (major > 2 || (major == 2 && minor > 0)) { + /* > 2.0 returns OK on QUIT and read() should be issued once more + * to know the descriptor is at EOF. */ + test_cond(strcasecmp(reply->str,"OK") == 0 && + redisGetReply(c,&_reply) == REDIS_ERR); + freeReplyObject(reply); + } else { + test_cond(reply == NULL); + } + + /* On 2.0, QUIT will cause the connection to be closed immediately and + * the read(2) for the reply on QUIT will set the error to EOF. + * On >2.0, QUIT will return with OK and another read(2) needed to be + * issued to find out the socket was closed by the server. In both + * conditions, the error will be set to EOF. */ + assert(c->err == REDIS_ERR_EOF && + strcmp(c->errstr,"Server closed the connection") == 0); + redisFree(c); + + c = connect(config); + test("Returns I/O error on socket timeout: "); + struct timeval tv = { 0, 1000 }; + assert(redisSetTimeout(c,tv) == REDIS_OK); + test_cond(redisGetReply(c,&_reply) == REDIS_ERR && + c->err == REDIS_ERR_IO && errno == EAGAIN); + redisFree(c); +} + +static void test_invalid_timeout_errors(struct config config) { + redisContext *c; + + test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: "); + + config.tcp.timeout.tv_sec = 0; + config.tcp.timeout.tv_usec = 10000001; + + c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); + + test_cond(c->err == REDIS_ERR_IO); + redisFree(c); + + test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); + + config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; + config.tcp.timeout.tv_usec = 0; + + c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); + + test_cond(c->err == REDIS_ERR_IO); + redisFree(c); +} + +static void test_throughput(struct config config) { + redisContext *c = connect(config); + redisReply **replies; + int i, num; + long long t1, t2; + + test("Throughput:\n"); + for (i = 0; i < 500; i++) + freeReplyObject(redisCommand(c,"LPUSH mylist foo")); + + num = 1000; + replies = malloc(sizeof(redisReply*)*num); + t1 = usec(); + for (i = 0; i < num; i++) { + replies[i] = redisCommand(c,"PING"); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + t1 = usec(); + for (i = 0; i < num; i++) { + replies[i] = redisCommand(c,"LRANGE mylist 0 499"); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); + assert(replies[i] != NULL && replies[i]->elements == 500); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); + + num = 10000; + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) + redisAppendCommand(c,"PING"); + t1 = usec(); + for (i = 0; i < num; i++) { + assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) + redisAppendCommand(c,"LRANGE mylist 0 499"); + t1 = usec(); + for (i = 0; i < num; i++) { + assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); + assert(replies[i] != NULL && replies[i]->elements == 500); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + + disconnect(c, 0); +} + +// static long __test_callback_flags = 0; +// static void __test_callback(redisContext *c, void *privdata) { +// ((void)c); +// /* Shift to detect execution order */ +// __test_callback_flags <<= 8; +// __test_callback_flags |= (long)privdata; +// } +// +// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) { +// ((void)c); +// /* Shift to detect execution order */ +// __test_callback_flags <<= 8; +// __test_callback_flags |= (long)privdata; +// if (reply) freeReplyObject(reply); +// } +// +// static redisContext *__connect_nonblock() { +// /* Reset callback flags */ +// __test_callback_flags = 0; +// return redisConnectNonBlock("127.0.0.1", port, NULL); +// } +// +// static void test_nonblocking_connection() { +// redisContext *c; +// int wdone = 0; +// +// test("Calls command callback when command is issued: "); +// c = __connect_nonblock(); +// redisSetCommandCallback(c,__test_callback,(void*)1); +// redisCommand(c,"PING"); +// test_cond(__test_callback_flags == 1); +// redisFree(c); +// +// test("Calls disconnect callback on redisDisconnect: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)2); +// redisDisconnect(c); +// test_cond(__test_callback_flags == 2); +// redisFree(c); +// +// test("Calls disconnect callback and free callback on redisFree: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)2); +// redisSetFreeCallback(c,__test_callback,(void*)4); +// redisFree(c); +// test_cond(__test_callback_flags == ((2 << 8) | 4)); +// +// test("redisBufferWrite against empty write buffer: "); +// c = __connect_nonblock(); +// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1); +// redisFree(c); +// +// test("redisBufferWrite against not yet connected fd: "); +// c = __connect_nonblock(); +// redisCommand(c,"PING"); +// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && +// strncmp(c->error,"write:",6) == 0); +// redisFree(c); +// +// test("redisBufferWrite against closed fd: "); +// c = __connect_nonblock(); +// redisCommand(c,"PING"); +// redisDisconnect(c); +// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && +// strncmp(c->error,"write:",6) == 0); +// redisFree(c); +// +// test("Process callbacks in the right sequence: "); +// c = __connect_nonblock(); +// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); +// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); +// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); +// +// /* Write output buffer */ +// wdone = 0; +// while(!wdone) { +// usleep(500); +// redisBufferWrite(c,&wdone); +// } +// +// /* Read until at least one callback is executed (the 3 replies will +// * arrive in a single packet, causing all callbacks to be executed in +// * a single pass). */ +// while(__test_callback_flags == 0) { +// assert(redisBufferRead(c) == REDIS_OK); +// redisProcessCallbacks(c); +// } +// test_cond(__test_callback_flags == 0x010203); +// redisFree(c); +// +// test("redisDisconnect executes pending callbacks with NULL reply: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)1); +// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); +// redisDisconnect(c); +// test_cond(__test_callback_flags == 0x0201); +// redisFree(c); +// } + +int main(int argc, char **argv) { + struct config cfg = { + .tcp = { + .host = "127.0.0.1", + .port = 6379 + }, + .unix = { + .path = "/tmp/redis.sock" + } + }; + int throughput = 1; + int test_inherit_fd = 1; + + /* Ignore broken pipe signal (for I/O error tests). */ + signal(SIGPIPE, SIG_IGN); + + /* Parse command line options. */ + argv++; argc--; + while (argc) { + if (argc >= 2 && !strcmp(argv[0],"-h")) { + argv++; argc--; + cfg.tcp.host = argv[0]; + } else if (argc >= 2 && !strcmp(argv[0],"-p")) { + argv++; argc--; + cfg.tcp.port = atoi(argv[0]); + } else if (argc >= 2 && !strcmp(argv[0],"-s")) { + argv++; argc--; + cfg.unix.path = argv[0]; + } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { + throughput = 0; + } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { + test_inherit_fd = 0; + } else { + fprintf(stderr, "Invalid argument: %s\n", argv[0]); + exit(1); + } + argv++; argc--; + } + + test_format_commands(); + test_reply_reader(); + test_blocking_connection_errors(); + test_free_null(); + + printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); + cfg.type = CONN_TCP; + test_blocking_connection(cfg); + test_blocking_connection_timeouts(cfg); + test_blocking_io_errors(cfg); + test_invalid_timeout_errors(cfg); + test_append_formatted_commands(cfg); + if (throughput) test_throughput(cfg); + + printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path); + cfg.type = CONN_UNIX; + test_blocking_connection(cfg); + test_blocking_connection_timeouts(cfg); + test_blocking_io_errors(cfg); + if (throughput) test_throughput(cfg); + + if (test_inherit_fd) { + printf("\nTesting against inherited fd (%s):\n", cfg.unix.path); + cfg.type = CONN_FD; + test_blocking_connection(cfg); + } + + + if (fails) { + printf("*** %d TESTS FAILED ***\n", fails); + return 1; + } + + printf("ALL TESTS PASSED\n"); + return 0; +} diff --git a/ext/hiredis-vip-0.3.0/win32.h b/ext/hiredis-vip-0.3.0/win32.h new file mode 100644 index 000000000..1a27c18f2 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/win32.h @@ -0,0 +1,42 @@ +#ifndef _WIN32_HELPER_INCLUDE +#define _WIN32_HELPER_INCLUDE +#ifdef _MSC_VER + +#ifndef inline +#define inline __inline +#endif + +#ifndef va_copy +#define va_copy(d,s) ((d) = (s)) +#endif + +#ifndef snprintf +#define snprintf c99_snprintf + +__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap) +{ + int count = -1; + + if (size != 0) + count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + + return count; +} + +__inline int c99_snprintf(char* str, size_t size, const char* format, ...) +{ + int count; + va_list ap; + + va_start(ap, format); + count = c99_vsnprintf(str, size, format, ap); + va_end(ap); + + return count; +} +#endif + +#endif +#endif \ No newline at end of file diff --git a/make-linux.mk b/make-linux.mk index e74f19303..dd8c5c520 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -260,6 +260,13 @@ ifeq ($(ZT_OFFICIAL),1) override LDFLAGS+=-Wl,--wrap=memcpy -static-libstdc++ endif +ifeq ($(ZT_CONTROLLER),1) + LDLIBS+=-L/usr/pgsql-10/lib/ -lpq -Lext/librabbitmq/centos_x64/lib/ -lrabbitmq + DEFS+=-DZT_CONTROLLER_USE_LIBPQ + INCLUDES+=-Iext/librabbitmq/macos/include -I/usr/pgsql-10/include -Iext/hiredis-vip-0.3.0 + ONE_OBJS+=ext/hiredis-vip-0.3.0/adlist.o ext/hiredis-vip-0.3.0/async.o ext/hiredis-vip-0.3.0/command.o ext/hiredis-vip-0.3.0/crc16.o ext/hiredis-vip-0.3.0/dict.o ext/hiredis-vip-0.3.0/hiarray.o ext/hiredis-vip-0.3.0/hircluster.o ext/hiredis-vip-0.3.0/hiredis.o ext/hiredis-vip-0.3.0/hiutil.o ext/hiredis-vip-0.3.0/net.o ext/hiredis-vip-0.3.0/read.o ext/hiredis-vip-0.3.0/sds.o +endif + # ARM32 hell -- use conservative CFLAGS ifeq ($(ZT_ARCHITECTURE),3) ifeq ($(shell if [ -e /usr/bin/dpkg ]; then dpkg --print-architecture; fi),armel) @@ -335,7 +342,7 @@ docker: FORCE docker build --no-cache -f ext/installfiles/linux/zerotier-containerized/Dockerfile -t zerotier-containerized . central-controller: FORCE - make -j4 LDLIBS="-L/usr/pgsql-10/lib/ -lpq -Lext/librabbitmq/centos_x64/lib/ -lrabbitmq" CXXFLAGS="-I/usr/pgsql-10/include -I./ext/librabbitmq/centos_x64/include -fPIC" DEFS="-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER" ZT_OFFICIAL=1 ZT_USE_X64_ASM_ED25519=1 one + make -j4 ZT_CONTROLLER=1 ZT_OFFICIAL=1 ZT_USE_X64_ASM_ED25519=1 one central-controller-docker: FORCE docker build --no-cache -t docker.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=`git name-rev --name-only HEAD` . diff --git a/make-mac.mk b/make-mac.mk index 35ca4a486..2dd232ff2 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -1,3 +1,4 @@ + CC=clang CXX=clang++ INCLUDES= @@ -29,7 +30,8 @@ ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o ext/http-parser/http ifeq ($(ZT_CONTROLLER),1) LIBS+=-L/usr/local/opt/libpq/lib -lpq -Lext/librabbitmq/macos/lib -lrabbitmq DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER - INCLUDES+=-Iext/librabbitmq/macos/include -I/usr/local/opt/libpq/include + INCLUDES+=-Iext/librabbitmq/macos/include -I/usr/local/opt/libpq/include -Iext/hiredis-vip-0.3.0 + ONE_OBJS+=ext/hiredis-vip-0.3.0/adlist.o ext/hiredis-vip-0.3.0/async.o ext/hiredis-vip-0.3.0/command.o ext/hiredis-vip-0.3.0/crc16.o ext/hiredis-vip-0.3.0/dict.o ext/hiredis-vip-0.3.0/hiarray.o ext/hiredis-vip-0.3.0/hircluster.o ext/hiredis-vip-0.3.0/hiredis.o ext/hiredis-vip-0.3.0/hiutil.o ext/hiredis-vip-0.3.0/net.o ext/hiredis-vip-0.3.0/read.o ext/hiredis-vip-0.3.0/sds.o endif # Official releases are signed with our Apple cert and apply software updates by default @@ -150,7 +152,8 @@ official: FORCE make ZT_OFFICIAL_RELEASE=1 mac-dist-pkg central-controller-docker: FORCE - docker build --no-cache -t docker.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . + #docker build --no-cache -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . + docker build -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . clean: rm -rf MacEthernetTapAgent *.dSYM build-* *.a *.pkg *.dmg *.o node/*.o controller/*.o service/*.o osdep/*.o ext/http-parser/*.o $(CORE_OBJS) $(ONE_OBJS) zerotier-one zerotier-idtool zerotier-selftest zerotier-cli zerotier doc/node_modules macui/build zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_* From 73b1d57b1398a469201f11b2a973d47f801c01f6 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 11 May 2020 12:29:06 -0700 Subject: [PATCH 047/362] rip out rabbitMQ --- controller/EmbeddedNetworkController.cpp | 7 +- controller/EmbeddedNetworkController.hpp | 6 +- controller/PostgreSQL.cpp | 100 +- controller/PostgreSQL.hpp | 8 +- controller/RabbitMQ.cpp | 120 - controller/RabbitMQ.hpp | 69 - ext/librabbitmq/centos_x64/include/amqp.h | 2538 ----------------- .../centos_x64/include/amqp_framing.h | 1144 -------- .../centos_x64/include/amqp_tcp_socket.h | 68 - ext/librabbitmq/centos_x64/lib/librabbitmq.a | Bin 139468 -> 0 bytes ext/librabbitmq/macos/include/amqp.h | 2538 ----------------- ext/librabbitmq/macos/include/amqp_framing.h | 1144 -------- .../macos/include/amqp_tcp_socket.h | 68 - ext/librabbitmq/macos/lib/librabbitmq.a | Bin 95704 -> 0 bytes make-linux.mk | 4 +- make-mac.mk | 4 +- objects.mk | 1 - service/OneService.cpp | 17 +- 18 files changed, 23 insertions(+), 7813 deletions(-) delete mode 100644 controller/RabbitMQ.cpp delete mode 100644 controller/RabbitMQ.hpp delete mode 100644 ext/librabbitmq/centos_x64/include/amqp.h delete mode 100644 ext/librabbitmq/centos_x64/include/amqp_framing.h delete mode 100644 ext/librabbitmq/centos_x64/include/amqp_tcp_socket.h delete mode 100644 ext/librabbitmq/centos_x64/lib/librabbitmq.a delete mode 100644 ext/librabbitmq/macos/include/amqp.h delete mode 100644 ext/librabbitmq/macos/include/amqp_framing.h delete mode 100644 ext/librabbitmq/macos/include/amqp_tcp_socket.h delete mode 100644 ext/librabbitmq/macos/lib/librabbitmq.a diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index e0e2a3eae..ef2ce2cfb 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -456,15 +456,14 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule) } // anonymous namespace -EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort, MQConfig *mqc) : +EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort) : _startTime(OSUtils::now()), _listenPort(listenPort), _node(node), _ztPath(ztPath), _path(dbPath), _sender((NetworkController::Sender *)0), - _db(this), - _mqc(mqc) + _db(this) { } @@ -485,7 +484,7 @@ void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender) #ifdef ZT_CONTROLLER_USE_LIBPQ if ((_path.length() > 9)&&(_path.substr(0,9) == "postgres:")) { - _db.addDB(std::shared_ptr(new PostgreSQL(_signingId,_path.substr(9).c_str(), _listenPort, _mqc))); + _db.addDB(std::shared_ptr(new PostgreSQL(_signingId,_path.substr(9).c_str(), _listenPort))); } else { #endif _db.addDB(std::shared_ptr(new FileDB(_path.c_str()))); diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index 1db4cf420..d09e08ac6 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -44,8 +44,6 @@ namespace ZeroTier { class Node; -struct MQConfig; - class EmbeddedNetworkController : public NetworkController,public DB::ChangeListener { public: @@ -53,7 +51,7 @@ public: * @param node Parent node * @param dbPath Database path (file path or database credentials) */ - EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort, MQConfig *mqc = NULL); + EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort); virtual ~EmbeddedNetworkController(); virtual void init(const Identity &signingId,Sender *sender); @@ -150,8 +148,6 @@ private: std::unordered_map< _MemberStatusKey,_MemberStatus,_MemberStatusHash > _memberStatus; std::mutex _memberStatus_l; - - MQConfig *_mqc; }; } // namespace ZeroTier diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index e81ac0a69..72acdca9e 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -17,13 +17,12 @@ #include "../node/Constants.hpp" #include "EmbeddedNetworkController.hpp" -#include "RabbitMQ.hpp" #include "../version.h" +#include "hiredis.h" #include #include -#include -#include + using json = nlohmann::json; @@ -69,7 +68,7 @@ std::string join(const std::vector &elements, const char * const se using namespace ZeroTier; -PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, MQConfig *mqc) +PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort) : DB() , _myId(myId) , _myAddress(myId.address()) @@ -78,7 +77,6 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, M , _run(1) , _waitNoticePrinted(false) , _listenPort(listenPort) - , _mqc(mqc) { char myAddress[64]; _myAddressStr = myId.address().toString(myAddress); @@ -593,7 +591,7 @@ void PostgreSQL::heartbeat() std::string build = std::to_string(ZEROTIER_ONE_VERSION_BUILD); std::string now = std::to_string(OSUtils::now()); std::string host_port = std::to_string(_listenPort); - std::string use_rabbitmq = (_mqc != NULL) ? "true" : "false"; + std::string use_rabbitmq = (false) ? "true" : "false"; const char *values[10] = { controllerId, hostname, @@ -645,10 +643,10 @@ void PostgreSQL::membersDbWatcher() initializeMembers(conn); - if (this->_mqc != NULL) { - PQfinish(conn); - conn = NULL; - _membersWatcher_RabbitMQ(); + if (false) { + // PQfinish(conn); + // conn = NULL; + // _membersWatcher_RabbitMQ(); } else { _membersWatcher_Postgres(conn); PQfinish(conn); @@ -703,43 +701,6 @@ void PostgreSQL::_membersWatcher_Postgres(PGconn *conn) { } } -void PostgreSQL::_membersWatcher_RabbitMQ() { - char buf[11] = {0}; - std::string qname = "member_"+ std::string(_myAddress.toString(buf)); - RabbitMQ rmq(_mqc, qname.c_str()); - try { - rmq.init(); - } catch (std::runtime_error &e) { - fprintf(stderr, "RABBITMQ ERROR: %s\n", e.what()); - exit(11); - } - while (_run == 1) { - try { - std::string msg = rmq.consume(); - // fprintf(stderr, "Got Member Update: %s\n", msg.c_str()); - if (msg.empty()) { - continue; - } - json tmp(json::parse(msg)); - json &ov = tmp["old_val"]; - json &nv = tmp["new_val"]; - json oldConfig, newConfig; - if (ov.is_object()) oldConfig = ov; - if (nv.is_object()) newConfig = nv; - if (oldConfig.is_object() || newConfig.is_object()) { - _memberChanged(oldConfig,newConfig,(this->_ready>=2)); - } - } catch (std::runtime_error &e) { - fprintf(stderr, "RABBITMQ ERROR member change: %s\n", e.what()); - break; - } catch(std::exception &e ) { - fprintf(stderr, "RABBITMQ ERROR member change: %s\n", e.what()); - } catch(...) { - fprintf(stderr, "RABBITMQ ERROR member change: unknown error\n"); - } - } -} - void PostgreSQL::_membersWatcher_Reids() { char buff[11] = {0}; @@ -756,10 +717,10 @@ void PostgreSQL::networksDbWatcher() initializeNetworks(conn); - if (this->_mqc != NULL) { - PQfinish(conn); - conn = NULL; - _networksWatcher_RabbitMQ(); + if (false) { + // PQfinish(conn); + // conn = NULL; + // _networksWatcher_RabbitMQ(); } else { _networksWatcher_Postgres(conn); PQfinish(conn); @@ -812,43 +773,6 @@ void PostgreSQL::_networksWatcher_Postgres(PGconn *conn) { } } -void PostgreSQL::_networksWatcher_RabbitMQ() { - char buf[11] = {0}; - std::string qname = "network_"+ std::string(_myAddress.toString(buf)); - RabbitMQ rmq(_mqc, qname.c_str()); - try { - rmq.init(); - } catch (std::runtime_error &e) { - fprintf(stderr, "RABBITMQ ERROR: %s\n", e.what()); - exit(11); - } - while (_run == 1) { - try { - std::string msg = rmq.consume(); - if (msg.empty()) { - continue; - } - // fprintf(stderr, "Got network update: %s\n", msg.c_str()); - json tmp(json::parse(msg)); - json &ov = tmp["old_val"]; - json &nv = tmp["new_val"]; - json oldConfig, newConfig; - if (ov.is_object()) oldConfig = ov; - if (nv.is_object()) newConfig = nv; - if (oldConfig.is_object()||newConfig.is_object()) { - _networkChanged(oldConfig,newConfig,(this->_ready >= 2)); - } - } catch (std::runtime_error &e) { - fprintf(stderr, "RABBITMQ ERROR: %s\n", e.what()); - break; - } catch (std::exception &e) { - fprintf(stderr, "RABBITMQ ERROR network watcher: %s\n", e.what()); - } catch(...) { - fprintf(stderr, "RABBITMQ ERROR network watcher: unknown error\n"); - } - } -} - void PostgreSQL::_networksWatcher_Redis() { } diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index d0a32edfc..dcad35e7d 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -26,8 +26,6 @@ typedef struct pg_conn PGconn; namespace ZeroTier { -struct MQConfig; - /** * A controller database driver that talks to PostgreSQL * @@ -37,7 +35,7 @@ struct MQConfig; class PostgreSQL : public DB { public: - PostgreSQL(const Identity &myId, const char *path, int listenPort, MQConfig *mqc = NULL); + PostgreSQL(const Identity &myId, const char *path, int listenPort); virtual ~PostgreSQL(); virtual bool waitForReady(); @@ -59,10 +57,8 @@ private: void heartbeat(); void membersDbWatcher(); void _membersWatcher_Postgres(PGconn *conn); - void _membersWatcher_RabbitMQ(); void networksDbWatcher(); void _networksWatcher_Postgres(PGconn *conn); - void _networksWatcher_RabbitMQ(); void _membersWatcher_Reids(); void _networksWatcher_Redis(); @@ -98,8 +94,6 @@ private: mutable volatile bool _waitNoticePrinted; int _listenPort; - - MQConfig *_mqc; }; } // namespace ZeroTier diff --git a/controller/RabbitMQ.cpp b/controller/RabbitMQ.cpp deleted file mode 100644 index 29a331ad2..000000000 --- a/controller/RabbitMQ.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c)2019 ZeroTier, Inc. - * - * Use of this software is governed by the Business Source License included - * in the LICENSE.TXT file in the project's root directory. - * - * Change Date: 2023-01-01 - * - * On the date above, in accordance with the Business Source License, use - * of this software will be governed by version 2.0 of the Apache License. - */ -/****/ - -#include "RabbitMQ.hpp" - -#ifdef ZT_CONTROLLER_USE_LIBPQ - -#include -#include -#include -#include - -namespace ZeroTier -{ - -RabbitMQ::RabbitMQ(MQConfig *cfg, const char *queueName) - : _mqc(cfg) - , _qName(queueName) - , _socket(NULL) - , _status(0) -{ -} - -RabbitMQ::~RabbitMQ() -{ - amqp_channel_close(_conn, _channel, AMQP_REPLY_SUCCESS); - amqp_connection_close(_conn, AMQP_REPLY_SUCCESS); - amqp_destroy_connection(_conn); -} - -void RabbitMQ::init() -{ - struct timeval tval; - memset(&tval, 0, sizeof(struct timeval)); - tval.tv_sec = 5; - - fprintf(stderr, "Initializing RabbitMQ %s\n", _qName); - _conn = amqp_new_connection(); - _socket = amqp_tcp_socket_new(_conn); - if (!_socket) { - throw std::runtime_error("Can't create socket for RabbitMQ"); - } - - _status = amqp_socket_open_noblock(_socket, _mqc->host.c_str(), _mqc->port, &tval); - if (_status) { - throw std::runtime_error("Can't connect to RabbitMQ"); - } - - amqp_rpc_reply_t r = amqp_login(_conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, - _mqc->username.c_str(), _mqc->password.c_str()); - if (r.reply_type != AMQP_RESPONSE_NORMAL) { - throw std::runtime_error("RabbitMQ Login Error"); - } - - static int chan = 0; - { - Mutex::Lock l(_chan_m); - _channel = ++chan; - } - amqp_channel_open(_conn, _channel); - r = amqp_get_rpc_reply(_conn); - if(r.reply_type != AMQP_RESPONSE_NORMAL) { - throw std::runtime_error("Error opening communication channel"); - } - - _q = amqp_queue_declare(_conn, _channel, amqp_cstring_bytes(_qName), 0, 0, 0, 0, amqp_empty_table); - r = amqp_get_rpc_reply(_conn); - if (r.reply_type != AMQP_RESPONSE_NORMAL) { - throw std::runtime_error("Error declaring queue " + std::string(_qName)); - } - - amqp_basic_consume(_conn, _channel, amqp_cstring_bytes(_qName), amqp_empty_bytes, 0, 1, 0, amqp_empty_table); - r = amqp_get_rpc_reply(_conn); - if (r.reply_type != AMQP_RESPONSE_NORMAL) { - throw std::runtime_error("Error consuming queue " + std::string(_qName)); - } - fprintf(stderr, "RabbitMQ Init OK %s\n", _qName); -} - -std::string RabbitMQ::consume() -{ - amqp_rpc_reply_t res; - amqp_envelope_t envelope; - amqp_maybe_release_buffers(_conn); - - struct timeval timeout; - timeout.tv_sec = 1; - timeout.tv_usec = 0; - - res = amqp_consume_message(_conn, &envelope, &timeout, 0); - if (res.reply_type != AMQP_RESPONSE_NORMAL) { - if (res.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION && res.library_error == AMQP_STATUS_TIMEOUT) { - // timeout waiting for message. Return empty string - return ""; - } else { - throw std::runtime_error("Error getting message"); - } - } - - std::string msg( - (const char*)envelope.message.body.bytes, - envelope.message.body.len - ); - amqp_destroy_envelope(&envelope); - return msg; -} - -} - -#endif // ZT_CONTROLLER_USE_LIBPQ diff --git a/controller/RabbitMQ.hpp b/controller/RabbitMQ.hpp deleted file mode 100644 index 4c286288e..000000000 --- a/controller/RabbitMQ.hpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c)2019 ZeroTier, Inc. - * - * Use of this software is governed by the Business Source License included - * in the LICENSE.TXT file in the project's root directory. - * - * Change Date: 2023-01-01 - * - * On the date above, in accordance with the Business Source License, use - * of this software will be governed by version 2.0 of the Apache License. - */ -/****/ - -#ifndef ZT_CONTROLLER_RABBITMQ_HPP -#define ZT_CONTROLLER_RABBITMQ_HPP - -#include "DB.hpp" -#include - -namespace ZeroTier -{ -struct MQConfig { - std::string host; - int port; - std::string username; - std::string password; -}; -} - -#ifdef ZT_CONTROLLER_USE_LIBPQ - -#include "../node/Mutex.hpp" - -#include -#include - - -namespace ZeroTier -{ - -class RabbitMQ { -public: - RabbitMQ(MQConfig *cfg, const char *queueName); - ~RabbitMQ(); - - void init(); - - std::string consume(); - -private: - MQConfig *_mqc; - const char *_qName; - - amqp_socket_t *_socket; - amqp_connection_state_t _conn; - amqp_queue_declare_ok_t *_q; - int _status; - - int _channel; - - Mutex _chan_m; -}; - -} - -#endif // ZT_CONTROLLER_USE_LIBPQ - -#endif // ZT_CONTROLLER_RABBITMQ_HPP - diff --git a/ext/librabbitmq/centos_x64/include/amqp.h b/ext/librabbitmq/centos_x64/include/amqp.h deleted file mode 100644 index 2983b1665..000000000 --- a/ext/librabbitmq/centos_x64/include/amqp.h +++ /dev/null @@ -1,2538 +0,0 @@ -/** \file */ -/* - * ***** BEGIN LICENSE BLOCK ***** - * Version: MIT - * - * Portions created by Alan Antonuk are Copyright (c) 2012-2014 - * Alan Antonuk. All Rights Reserved. - * - * Portions created by VMware are Copyright (c) 2007-2012 VMware, Inc. - * All Rights Reserved. - * - * Portions created by Tony Garnock-Jones are Copyright (c) 2009-2010 - * VMware, Inc. and Tony Garnock-Jones. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * ***** END LICENSE BLOCK ***** - */ - -#ifndef AMQP_H -#define AMQP_H - -/** \cond HIDE_FROM_DOXYGEN */ - -#ifdef __cplusplus -#define AMQP_BEGIN_DECLS extern "C" { -#define AMQP_END_DECLS } -#else -#define AMQP_BEGIN_DECLS -#define AMQP_END_DECLS -#endif - -/* - * \internal - * Important API decorators: - * AMQP_PUBLIC_FUNCTION - a public API function - * AMQP_PUBLIC_VARIABLE - a public API external variable - * AMQP_CALL - calling convension (used on Win32) - */ - -#if defined(_WIN32) && defined(_MSC_VER) -#if defined(AMQP_BUILD) && !defined(AMQP_STATIC) -#define AMQP_PUBLIC_FUNCTION __declspec(dllexport) -#define AMQP_PUBLIC_VARIABLE __declspec(dllexport) extern -#else -#define AMQP_PUBLIC_FUNCTION -#if !defined(AMQP_STATIC) -#define AMQP_PUBLIC_VARIABLE __declspec(dllimport) extern -#else -#define AMQP_PUBLIC_VARIABLE extern -#endif -#endif -#define AMQP_CALL __cdecl - -#elif defined(_WIN32) && defined(__BORLANDC__) -#if defined(AMQP_BUILD) && !defined(AMQP_STATIC) -#define AMQP_PUBLIC_FUNCTION __declspec(dllexport) -#define AMQP_PUBLIC_VARIABLE __declspec(dllexport) extern -#else -#define AMQP_PUBLIC_FUNCTION -#if !defined(AMQP_STATIC) -#define AMQP_PUBLIC_VARIABLE __declspec(dllimport) extern -#else -#define AMQP_PUBLIC_VARIABLE extern -#endif -#endif -#define AMQP_CALL __cdecl - -#elif defined(_WIN32) && defined(__MINGW32__) -#if defined(AMQP_BUILD) && !defined(AMQP_STATIC) -#define AMQP_PUBLIC_FUNCTION __declspec(dllexport) -#define AMQP_PUBLIC_VARIABLE __declspec(dllexport) extern -#else -#define AMQP_PUBLIC_FUNCTION -#if !defined(AMQP_STATIC) -#define AMQP_PUBLIC_VARIABLE __declspec(dllimport) extern -#else -#define AMQP_PUBLIC_VARIABLE extern -#endif -#endif -#define AMQP_CALL __cdecl - -#elif defined(_WIN32) && defined(__CYGWIN__) -#if defined(AMQP_BUILD) && !defined(AMQP_STATIC) -#define AMQP_PUBLIC_FUNCTION __declspec(dllexport) -#define AMQP_PUBLIC_VARIABLE __declspec(dllexport) -#else -#define AMQP_PUBLIC_FUNCTION -#if !defined(AMQP_STATIC) -#define AMQP_PUBLIC_VARIABLE __declspec(dllimport) extern -#else -#define AMQP_PUBLIC_VARIABLE extern -#endif -#endif -#define AMQP_CALL __cdecl - -#elif defined(__GNUC__) && __GNUC__ >= 4 -#define AMQP_PUBLIC_FUNCTION __attribute__((visibility("default"))) -#define AMQP_PUBLIC_VARIABLE __attribute__((visibility("default"))) extern -#define AMQP_CALL -#else -#define AMQP_PUBLIC_FUNCTION -#define AMQP_PUBLIC_VARIABLE extern -#define AMQP_CALL -#endif - -#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) -#define AMQP_DEPRECATED(function) function __attribute__((__deprecated__)) -#elif defined(_MSC_VER) -#define AMQP_DEPRECATED(function) __declspec(deprecated) function -#else -#define AMQP_DEPRECATED(function) -#endif - -/* Define ssize_t on Win32/64 platforms - See: http://lists.cs.uiuc.edu/pipermail/llvmdev/2010-April/030649.html for - details - */ -#if !defined(_W64) -#if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 -#define _W64 __w64 -#else -#define _W64 -#endif -#endif - -#ifdef _MSC_VER -#ifdef _WIN64 -typedef __int64 ssize_t; -#else -typedef _W64 int ssize_t; -#endif -#endif - -#if defined(_WIN32) && defined(__MINGW32__) -#include -#endif - -/** \endcond */ - -#include -#include - -struct timeval; - -AMQP_BEGIN_DECLS - -/** - * \def AMQP_VERSION_MAJOR - * - * Major library version number compile-time constant - * - * The major version is incremented when backwards incompatible API changes - * are made. - * - * \sa AMQP_VERSION, AMQP_VERSION_STRING - * - * \since v0.4.0 - */ - -/** - * \def AMQP_VERSION_MINOR - * - * Minor library version number compile-time constant - * - * The minor version is incremented when new APIs are added. Existing APIs - * are left alone. - * - * \sa AMQP_VERSION, AMQP_VERSION_STRING - * - * \since v0.4.0 - */ - -/** - * \def AMQP_VERSION_PATCH - * - * Patch library version number compile-time constant - * - * The patch version is incremented when library code changes, but the API - * is not changed. - * - * \sa AMQP_VERSION, AMQP_VERSION_STRING - * - * \since v0.4.0 - */ - -/** - * \def AMQP_VERSION_IS_RELEASE - * - * Version constant set to 1 for tagged release, 0 otherwise - * - * NOTE: versions that are not tagged releases are not guaranteed to be API/ABI - * compatible with older releases, and may change commit-to-commit. - * - * \sa AMQP_VERSION, AMQP_VERSION_STRING - * - * \since v0.4.0 - */ -/* - * Developer note: when changing these, be sure to update SOVERSION constants - * in CMakeLists.txt and configure.ac - */ - -#define AMQP_VERSION_MAJOR 0 -#define AMQP_VERSION_MINOR 10 -#define AMQP_VERSION_PATCH 0 -#define AMQP_VERSION_IS_RELEASE 0 - -/** - * \def AMQP_VERSION_CODE - * - * Helper macro to geneate a packed version code suitable for - * comparison with AMQP_VERSION. - * - * \sa amqp_version_number() AMQP_VERSION_MAJOR, AMQP_VERSION_MINOR, - * AMQP_VERSION_PATCH, AMQP_VERSION_IS_RELEASE, AMQP_VERSION - * - * \since v0.6.1 - */ -#define AMQP_VERSION_CODE(major, minor, patch, release) \ - ((major << 24) | (minor << 16) | (patch << 8) | (release)) - -/** - * \def AMQP_VERSION - * - * Packed version number - * - * AMQP_VERSION is a 4-byte unsigned integer with the most significant byte - * set to AMQP_VERSION_MAJOR, the second most significant byte set to - * AMQP_VERSION_MINOR, third most significant byte set to AMQP_VERSION_PATCH, - * and the lowest byte set to AMQP_VERSION_IS_RELEASE. - * - * For example version 2.3.4 which is released version would be encoded as - * 0x02030401 - * - * \sa amqp_version_number() AMQP_VERSION_MAJOR, AMQP_VERSION_MINOR, - * AMQP_VERSION_PATCH, AMQP_VERSION_IS_RELEASE, AMQP_VERSION_CODE - * - * \since v0.4.0 - */ -#define AMQP_VERSION \ - AMQP_VERSION_CODE(AMQP_VERSION_MAJOR, AMQP_VERSION_MINOR, \ - AMQP_VERSION_PATCH, AMQP_VERSION_IS_RELEASE) - -/** \cond HIDE_FROM_DOXYGEN */ -#define AMQ_STRINGIFY(s) AMQ_STRINGIFY_HELPER(s) -#define AMQ_STRINGIFY_HELPER(s) #s - -#define AMQ_VERSION_STRING \ - AMQ_STRINGIFY(AMQP_VERSION_MAJOR) \ - "." AMQ_STRINGIFY(AMQP_VERSION_MINOR) "." AMQ_STRINGIFY(AMQP_VERSION_PATCH) -/** \endcond */ - -/** - * \def AMQP_VERSION_STRING - * - * Version string compile-time constant - * - * Non-released versions of the library will have "-pre" appended to the - * version string - * - * \sa amqp_version() - * - * \since v0.4.0 - */ -#if AMQP_VERSION_IS_RELEASE -#define AMQP_VERSION_STRING AMQ_VERSION_STRING -#else -#define AMQP_VERSION_STRING AMQ_VERSION_STRING "-pre" -#endif - -/** - * Returns the rabbitmq-c version as a packed integer. - * - * See \ref AMQP_VERSION - * - * \return packed 32-bit integer representing version of library at runtime - * - * \sa AMQP_VERSION, amqp_version() - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -uint32_t AMQP_CALL amqp_version_number(void); - -/** - * Returns the rabbitmq-c version as a string. - * - * See \ref AMQP_VERSION_STRING - * - * \return a statically allocated string describing the version of rabbitmq-c. - * - * \sa amqp_version_number(), AMQP_VERSION_STRING, AMQP_VERSION - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -char const *AMQP_CALL amqp_version(void); - -/** - * \def AMQP_DEFAULT_FRAME_SIZE - * - * Default frame size (128Kb) - * - * \sa amqp_login(), amqp_login_with_properties() - * - * \since v0.4.0 - */ -#define AMQP_DEFAULT_FRAME_SIZE 131072 - -/** - * \def AMQP_DEFAULT_MAX_CHANNELS - * - * Default maximum number of channels (2047, RabbitMQ default limit of 2048, - * minus 1 for channel 0). RabbitMQ set a default limit of 2048 channels per - * connection in v3.7.5 to prevent broken clients from leaking too many - * channels. - * - * \sa amqp_login(), amqp_login_with_properties() - * - * \since v0.4.0 - */ -#define AMQP_DEFAULT_MAX_CHANNELS 2047 - -/** - * \def AMQP_DEFAULT_HEARTBEAT - * - * Default heartbeat interval (0, heartbeat disabled) - * - * \sa amqp_login(), amqp_login_with_properties() - * - * \since v0.4.0 - */ -#define AMQP_DEFAULT_HEARTBEAT 0 - -/** - * \def AMQP_DEFAULT_VHOST - * - * Default RabbitMQ vhost: "/" - * - * \sa amqp_login(), amqp_login_with_properties() - * - * \since v0.9.0 - */ -#define AMQP_DEFAULT_VHOST "/" - -/** - * boolean type 0 = false, true otherwise - * - * \since v0.1 - */ -typedef int amqp_boolean_t; - -/** - * Method number - * - * \since v0.1 - */ -typedef uint32_t amqp_method_number_t; - -/** - * Bitmask for flags - * - * \since v0.1 - */ -typedef uint32_t amqp_flags_t; - -/** - * Channel type - * - * \since v0.1 - */ -typedef uint16_t amqp_channel_t; - -/** - * Buffer descriptor - * - * \since v0.1 - */ -typedef struct amqp_bytes_t_ { - size_t len; /**< length of the buffer in bytes */ - void *bytes; /**< pointer to the beginning of the buffer */ -} amqp_bytes_t; - -/** - * Decimal data type - * - * \since v0.1 - */ -typedef struct amqp_decimal_t_ { - uint8_t decimals; /**< the location of the decimal point */ - uint32_t value; /**< the value before the decimal point is applied */ -} amqp_decimal_t; - -/** - * AMQP field table - * - * An AMQP field table is a set of key-value pairs. - * A key is a UTF-8 encoded string up to 128 bytes long, and are not null - * terminated. - * A value can be one of several different datatypes. \sa - * amqp_field_value_kind_t - * - * \sa amqp_table_entry_t - * - * \since v0.1 - */ -typedef struct amqp_table_t_ { - int num_entries; /**< length of entries array */ - struct amqp_table_entry_t_ *entries; /**< an array of table entries */ -} amqp_table_t; - -/** - * An AMQP Field Array - * - * A repeated set of field values, all must be of the same type - * - * \since v0.1 - */ -typedef struct amqp_array_t_ { - int num_entries; /**< Number of entries in the table */ - struct amqp_field_value_t_ *entries; /**< linked list of field values */ -} amqp_array_t; - -/* - 0-9 0-9-1 Qpid/Rabbit Type Remarks ---------------------------------------------------------------------------- - t t Boolean - b b Signed 8-bit - B Unsigned 8-bit - U s Signed 16-bit (A1) - u Unsigned 16-bit - I I I Signed 32-bit - i Unsigned 32-bit - L l Signed 64-bit (B) - l Unsigned 64-bit - f f 32-bit float - d d 64-bit float - D D D Decimal - s Short string (A2) - S S S Long string - A Nested Array - T T T Timestamp (u64) - F F F Nested Table - V V V Void - x Byte array - -Remarks: - - A1, A2: Notice how the types **CONFLICT** here. In Qpid and Rabbit, - 's' means a signed 16-bit integer; in 0-9-1, it means a - short string. - - B: Notice how the signednesses **CONFLICT** here. In Qpid and Rabbit, - 'l' means a signed 64-bit integer; in 0-9-1, it means an unsigned - 64-bit integer. - -I'm going with the Qpid/Rabbit types, where there's a conflict, and -the 0-9-1 types otherwise. 0-8 is a subset of 0-9, which is a subset -of the other two, so this will work for both 0-8 and 0-9-1 branches of -the code. -*/ - -/** - * A field table value - * - * \since v0.1 - */ -typedef struct amqp_field_value_t_ { - uint8_t kind; /**< the type of the entry /sa amqp_field_value_kind_t */ - union { - amqp_boolean_t boolean; /**< boolean type AMQP_FIELD_KIND_BOOLEAN */ - int8_t i8; /**< int8_t type AMQP_FIELD_KIND_I8 */ - uint8_t u8; /**< uint8_t type AMQP_FIELD_KIND_U8 */ - int16_t i16; /**< int16_t type AMQP_FIELD_KIND_I16 */ - uint16_t u16; /**< uint16_t type AMQP_FIELD_KIND_U16 */ - int32_t i32; /**< int32_t type AMQP_FIELD_KIND_I32 */ - uint32_t u32; /**< uint32_t type AMQP_FIELD_KIND_U32 */ - int64_t i64; /**< int64_t type AMQP_FIELD_KIND_I64 */ - uint64_t u64; /**< uint64_t type AMQP_FIELD_KIND_U64, - AMQP_FIELD_KIND_TIMESTAMP */ - float f32; /**< float type AMQP_FIELD_KIND_F32 */ - double f64; /**< double type AMQP_FIELD_KIND_F64 */ - amqp_decimal_t decimal; /**< amqp_decimal_t AMQP_FIELD_KIND_DECIMAL */ - amqp_bytes_t bytes; /**< amqp_bytes_t type AMQP_FIELD_KIND_UTF8, - AMQP_FIELD_KIND_BYTES */ - amqp_table_t table; /**< amqp_table_t type AMQP_FIELD_KIND_TABLE */ - amqp_array_t array; /**< amqp_array_t type AMQP_FIELD_KIND_ARRAY */ - } value; /**< a union of the value */ -} amqp_field_value_t; - -/** - * An entry in a field-table - * - * \sa amqp_table_encode(), amqp_table_decode(), amqp_table_clone() - * - * \since v0.1 - */ -typedef struct amqp_table_entry_t_ { - amqp_bytes_t key; /**< the table entry key. Its a null-terminated UTF-8 - * string, with a maximum size of 128 bytes */ - amqp_field_value_t value; /**< the table entry values */ -} amqp_table_entry_t; - -/** - * Field value types - * - * \since v0.1 - */ -typedef enum { - AMQP_FIELD_KIND_BOOLEAN = - 't', /**< boolean type. 0 = false, 1 = true @see amqp_boolean_t */ - AMQP_FIELD_KIND_I8 = 'b', /**< 8-bit signed integer, datatype: int8_t */ - AMQP_FIELD_KIND_U8 = 'B', /**< 8-bit unsigned integer, datatype: uint8_t */ - AMQP_FIELD_KIND_I16 = 's', /**< 16-bit signed integer, datatype: int16_t */ - AMQP_FIELD_KIND_U16 = 'u', /**< 16-bit unsigned integer, datatype: uint16_t */ - AMQP_FIELD_KIND_I32 = 'I', /**< 32-bit signed integer, datatype: int32_t */ - AMQP_FIELD_KIND_U32 = 'i', /**< 32-bit unsigned integer, datatype: uint32_t */ - AMQP_FIELD_KIND_I64 = 'l', /**< 64-bit signed integer, datatype: int64_t */ - AMQP_FIELD_KIND_U64 = 'L', /**< 64-bit unsigned integer, datatype: uint64_t */ - AMQP_FIELD_KIND_F32 = - 'f', /**< single-precision floating point value, datatype: float */ - AMQP_FIELD_KIND_F64 = - 'd', /**< double-precision floating point value, datatype: double */ - AMQP_FIELD_KIND_DECIMAL = - 'D', /**< amqp-decimal value, datatype: amqp_decimal_t */ - AMQP_FIELD_KIND_UTF8 = 'S', /**< UTF-8 null-terminated character string, - datatype: amqp_bytes_t */ - AMQP_FIELD_KIND_ARRAY = 'A', /**< field array (repeated values of another - datatype. datatype: amqp_array_t */ - AMQP_FIELD_KIND_TIMESTAMP = 'T', /**< 64-bit timestamp. datatype uint64_t */ - AMQP_FIELD_KIND_TABLE = 'F', /**< field table. encapsulates a table inside a - table entry. datatype: amqp_table_t */ - AMQP_FIELD_KIND_VOID = 'V', /**< empty entry */ - AMQP_FIELD_KIND_BYTES = - 'x' /**< unformatted byte string, datatype: amqp_bytes_t */ -} amqp_field_value_kind_t; - -/** - * A list of allocation blocks - * - * \since v0.1 - */ -typedef struct amqp_pool_blocklist_t_ { - int num_blocks; /**< Number of blocks in the block list */ - void **blocklist; /**< Array of memory blocks */ -} amqp_pool_blocklist_t; - -/** - * A memory pool - * - * \since v0.1 - */ -typedef struct amqp_pool_t_ { - size_t pagesize; /**< the size of the page in bytes. Allocations less than or - * equal to this size are allocated in the pages block list. - * Allocations greater than this are allocated in their own - * own block in the large_blocks block list */ - - amqp_pool_blocklist_t pages; /**< blocks that are the size of pagesize */ - amqp_pool_blocklist_t - large_blocks; /**< allocations larger than the pagesize */ - - int next_page; /**< an index to the next unused page block */ - char *alloc_block; /**< pointer to the current allocation block */ - size_t alloc_used; /**< number of bytes in the current allocation block that - has been used */ -} amqp_pool_t; - -/** - * An amqp method - * - * \since v0.1 - */ -typedef struct amqp_method_t_ { - amqp_method_number_t id; /**< the method id number */ - void *decoded; /**< pointer to the decoded method, - * cast to the appropriate type to use */ -} amqp_method_t; - -/** - * An AMQP frame - * - * \since v0.1 - */ -typedef struct amqp_frame_t_ { - uint8_t frame_type; /**< frame type. The types: - * - AMQP_FRAME_METHOD - use the method union member - * - AMQP_FRAME_HEADER - use the properties union member - * - AMQP_FRAME_BODY - use the body_fragment union member - */ - amqp_channel_t channel; /**< the channel the frame was received on */ - union { - amqp_method_t - method; /**< a method, use if frame_type == AMQP_FRAME_METHOD */ - struct { - uint16_t class_id; /**< the class for the properties */ - uint64_t body_size; /**< size of the body in bytes */ - void *decoded; /**< the decoded properties */ - amqp_bytes_t raw; /**< amqp-encoded properties structure */ - } properties; /**< message header, a.k.a., properties, - use if frame_type == AMQP_FRAME_HEADER */ - amqp_bytes_t body_fragment; /**< a body fragment, use if frame_type == - AMQP_FRAME_BODY */ - struct { - uint8_t transport_high; /**< @internal first byte of handshake */ - uint8_t transport_low; /**< @internal second byte of handshake */ - uint8_t protocol_version_major; /**< @internal third byte of handshake */ - uint8_t protocol_version_minor; /**< @internal fourth byte of handshake */ - } protocol_header; /**< Used only when doing the initial handshake with the - broker, don't use otherwise */ - } payload; /**< the payload of the frame */ -} amqp_frame_t; - -/** - * Response type - * - * \since v0.1 - */ -typedef enum amqp_response_type_enum_ { - AMQP_RESPONSE_NONE = 0, /**< the library got an EOF from the socket */ - AMQP_RESPONSE_NORMAL, /**< response normal, the RPC completed successfully */ - AMQP_RESPONSE_LIBRARY_EXCEPTION, /**< library error, an error occurred in the - library, examine the library_error */ - AMQP_RESPONSE_SERVER_EXCEPTION /**< server exception, the broker returned an - error, check replay */ -} amqp_response_type_enum; - -/** - * Reply from a RPC method on the broker - * - * \since v0.1 - */ -typedef struct amqp_rpc_reply_t_ { - amqp_response_type_enum reply_type; /**< the reply type: - * - AMQP_RESPONSE_NORMAL - the RPC - * completed successfully - * - AMQP_RESPONSE_SERVER_EXCEPTION - the - * broker returned - * an exception, check the reply field - * - AMQP_RESPONSE_LIBRARY_EXCEPTION - the - * library - * encountered an error, check the - * library_error field - */ - amqp_method_t reply; /**< in case of AMQP_RESPONSE_SERVER_EXCEPTION this - * field will be set to the method returned from the - * broker */ - int library_error; /**< in case of AMQP_RESPONSE_LIBRARY_EXCEPTION this - * field will be set to an error code. An error - * string can be retrieved using amqp_error_string */ -} amqp_rpc_reply_t; - -/** - * SASL method type - * - * \since v0.1 - */ -typedef enum amqp_sasl_method_enum_ { - AMQP_SASL_METHOD_UNDEFINED = -1, /**< Invalid SASL method */ - AMQP_SASL_METHOD_PLAIN = - 0, /**< the PLAIN SASL method for authentication to the broker */ - AMQP_SASL_METHOD_EXTERNAL = - 1 /**< the EXTERNAL SASL method for authentication to the broker */ -} amqp_sasl_method_enum; - -/** - * connection state object - * - * \since v0.1 - */ -typedef struct amqp_connection_state_t_ *amqp_connection_state_t; - -/** - * Socket object - * - * \since v0.4.0 - */ -typedef struct amqp_socket_t_ amqp_socket_t; - -/** - * Status codes - * - * \since v0.4.0 - */ -/* NOTE: When updating this enum, update the strings in librabbitmq/amqp_api.c - */ -typedef enum amqp_status_enum_ { - AMQP_STATUS_OK = 0x0, /**< Operation successful */ - AMQP_STATUS_NO_MEMORY = -0x0001, /**< Memory allocation - failed */ - AMQP_STATUS_BAD_AMQP_DATA = -0x0002, /**< Incorrect or corrupt - data was received from - the broker. This is a - protocol error. */ - AMQP_STATUS_UNKNOWN_CLASS = -0x0003, /**< An unknown AMQP class - was received. This is - a protocol error. */ - AMQP_STATUS_UNKNOWN_METHOD = -0x0004, /**< An unknown AMQP method - was received. This is - a protocol error. */ - AMQP_STATUS_HOSTNAME_RESOLUTION_FAILED = -0x0005, /**< Unable to resolve the - * hostname */ - AMQP_STATUS_INCOMPATIBLE_AMQP_VERSION = -0x0006, /**< The broker advertised - an incompaible AMQP - version */ - AMQP_STATUS_CONNECTION_CLOSED = -0x0007, /**< The connection to the - broker has been closed - */ - AMQP_STATUS_BAD_URL = -0x0008, /**< malformed AMQP URL */ - AMQP_STATUS_SOCKET_ERROR = -0x0009, /**< A socket error - occurred */ - AMQP_STATUS_INVALID_PARAMETER = -0x000A, /**< An invalid parameter - was passed into the - function */ - AMQP_STATUS_TABLE_TOO_BIG = -0x000B, /**< The amqp_table_t object - cannot be serialized - because the output - buffer is too small */ - AMQP_STATUS_WRONG_METHOD = -0x000C, /**< The wrong method was - received */ - AMQP_STATUS_TIMEOUT = -0x000D, /**< Operation timed out */ - AMQP_STATUS_TIMER_FAILURE = -0x000E, /**< The underlying system - timer facility failed */ - AMQP_STATUS_HEARTBEAT_TIMEOUT = -0x000F, /**< Timed out waiting for - heartbeat */ - AMQP_STATUS_UNEXPECTED_STATE = -0x0010, /**< Unexpected protocol - state */ - AMQP_STATUS_SOCKET_CLOSED = -0x0011, /**< Underlying socket is - closed */ - AMQP_STATUS_SOCKET_INUSE = -0x0012, /**< Underlying socket is - already open */ - AMQP_STATUS_BROKER_UNSUPPORTED_SASL_METHOD = -0x0013, /**< Broker does not - support the requested - SASL mechanism */ - AMQP_STATUS_UNSUPPORTED = -0x0014, /**< Parameter is unsupported - in this version */ - _AMQP_STATUS_NEXT_VALUE = -0x0015, /**< Internal value */ - - AMQP_STATUS_TCP_ERROR = -0x0100, /**< A generic TCP error - occurred */ - AMQP_STATUS_TCP_SOCKETLIB_INIT_ERROR = -0x0101, /**< An error occurred trying - to initialize the - socket library*/ - _AMQP_STATUS_TCP_NEXT_VALUE = -0x0102, /**< Internal value */ - - AMQP_STATUS_SSL_ERROR = -0x0200, /**< A generic SSL error - occurred. */ - AMQP_STATUS_SSL_HOSTNAME_VERIFY_FAILED = -0x0201, /**< SSL validation of - hostname against - peer certificate - failed */ - AMQP_STATUS_SSL_PEER_VERIFY_FAILED = -0x0202, /**< SSL validation of peer - certificate failed. */ - AMQP_STATUS_SSL_CONNECTION_FAILED = -0x0203, /**< SSL handshake failed. */ - _AMQP_STATUS_SSL_NEXT_VALUE = -0x0204 /**< Internal value */ -} amqp_status_enum; - -/** - * AMQP delivery modes. - * Use these values for the #amqp_basic_properties_t::delivery_mode field. - * - * \since v0.5 - */ -typedef enum { - AMQP_DELIVERY_NONPERSISTENT = 1, /**< Non-persistent message */ - AMQP_DELIVERY_PERSISTENT = 2 /**< Persistent message */ -} amqp_delivery_mode_enum; - -AMQP_END_DECLS - -#include - -AMQP_BEGIN_DECLS - -/** - * Empty bytes structure - * - * \since v0.2 - */ -AMQP_PUBLIC_VARIABLE const amqp_bytes_t amqp_empty_bytes; - -/** - * Empty table structure - * - * \since v0.2 - */ -AMQP_PUBLIC_VARIABLE const amqp_table_t amqp_empty_table; - -/** - * Empty table array structure - * - * \since v0.2 - */ -AMQP_PUBLIC_VARIABLE const amqp_array_t amqp_empty_array; - -/* Compatibility macros for the above, to avoid the need to update - code written against earlier versions of librabbitmq. */ - -/** - * \def AMQP_EMPTY_BYTES - * - * Deprecated, use \ref amqp_empty_bytes instead - * - * \deprecated use \ref amqp_empty_bytes instead - * - * \since v0.1 - */ -#define AMQP_EMPTY_BYTES amqp_empty_bytes - -/** - * \def AMQP_EMPTY_TABLE - * - * Deprecated, use \ref amqp_empty_table instead - * - * \deprecated use \ref amqp_empty_table instead - * - * \since v0.1 - */ -#define AMQP_EMPTY_TABLE amqp_empty_table - -/** - * \def AMQP_EMPTY_ARRAY - * - * Deprecated, use \ref amqp_empty_array instead - * - * \deprecated use \ref amqp_empty_array instead - * - * \since v0.1 - */ -#define AMQP_EMPTY_ARRAY amqp_empty_array - -/** - * Initializes an amqp_pool_t memory allocation pool for use - * - * Readies an allocation pool for use. An amqp_pool_t - * must be initialized before use - * - * \param [in] pool the amqp_pool_t structure to initialize. - * Calling this function on a pool a pool that has - * already been initialized will result in undefined - * behavior - * \param [in] pagesize the unit size that the pool will allocate - * memory chunks in. Anything allocated against the pool - * with a requested size will be carved out of a block - * this size. Allocations larger than this will be - * allocated individually - * - * \sa recycle_amqp_pool(), empty_amqp_pool(), amqp_pool_alloc(), - * amqp_pool_alloc_bytes(), amqp_pool_t - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL init_amqp_pool(amqp_pool_t *pool, size_t pagesize); - -/** - * Recycles an amqp_pool_t memory allocation pool - * - * Recycles the space allocate by the pool - * - * This invalidates all allocations made against the pool before this call is - * made, any use of any allocations made before recycle_amqp_pool() is called - * will result in undefined behavior. - * - * Note: this may or may not release memory, to force memory to be released - * call empty_amqp_pool(). - * - * \param [in] pool the amqp_pool_t to recycle - * - * \sa recycle_amqp_pool(), empty_amqp_pool(), amqp_pool_alloc(), - * amqp_pool_alloc_bytes() - * - * \since v0.1 - * - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL recycle_amqp_pool(amqp_pool_t *pool); - -/** - * Empties an amqp memory pool - * - * Releases all memory associated with an allocation pool - * - * \param [in] pool the amqp_pool_t to empty - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL empty_amqp_pool(amqp_pool_t *pool); - -/** - * Allocates a block of memory from an amqp_pool_t memory pool - * - * Memory will be aligned on a 8-byte boundary. If a 0-length allocation is - * requested, a NULL pointer will be returned. - * - * \param [in] pool the allocation pool to allocate the memory from - * \param [in] amount the size of the allocation in bytes. - * \return a pointer to the memory block, or NULL if the allocation cannot - * be satisfied. - * - * \sa init_amqp_pool(), recycle_amqp_pool(), empty_amqp_pool(), - * amqp_pool_alloc_bytes() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void *AMQP_CALL amqp_pool_alloc(amqp_pool_t *pool, size_t amount); - -/** - * Allocates a block of memory from an amqp_pool_t to an amqp_bytes_t - * - * Memory will be aligned on a 8-byte boundary. If a 0-length allocation is - * requested, output.bytes = NULL. - * - * \param [in] pool the allocation pool to allocate the memory from - * \param [in] amount the size of the allocation in bytes - * \param [in] output the location to store the pointer. On success - * output.bytes will be set to the beginning of the buffer - * output.len will be set to amount - * On error output.bytes will be set to NULL and output.len - * set to 0 - * - * \sa init_amqp_pool(), recycle_amqp_pool(), empty_amqp_pool(), - * amqp_pool_alloc() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_pool_alloc_bytes(amqp_pool_t *pool, size_t amount, - amqp_bytes_t *output); - -/** - * Wraps a c string in an amqp_bytes_t - * - * Takes a string, calculates its length and creates an - * amqp_bytes_t that points to it. The string is not duplicated. - * - * For a given input cstr, The amqp_bytes_t output.bytes is the - * same as cstr, output.len is the length of the string not including - * the \0 terminator - * - * This function uses strlen() internally so cstr must be properly - * terminated - * - * \param [in] cstr the c string to wrap - * \return an amqp_bytes_t that describes the string - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_bytes_t AMQP_CALL amqp_cstring_bytes(char const *cstr); - -/** - * Duplicates an amqp_bytes_t buffer. - * - * The buffer is cloned and the contents copied. - * - * The memory associated with the output is allocated - * with amqp_bytes_malloc() and should be freed with - * amqp_bytes_free() - * - * \param [in] src - * \return a clone of the src - * - * \sa amqp_bytes_free(), amqp_bytes_malloc() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_bytes_t AMQP_CALL amqp_bytes_malloc_dup(amqp_bytes_t src); - -/** - * Allocates a amqp_bytes_t buffer - * - * Creates an amqp_bytes_t buffer of the specified amount, the buffer should be - * freed using amqp_bytes_free() - * - * \param [in] amount the size of the buffer in bytes - * \returns an amqp_bytes_t with amount bytes allocated. - * output.bytes will be set to NULL on error - * - * \sa amqp_bytes_free(), amqp_bytes_malloc_dup() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_bytes_t AMQP_CALL amqp_bytes_malloc(size_t amount); - -/** - * Frees an amqp_bytes_t buffer - * - * Frees a buffer allocated with amqp_bytes_malloc() or amqp_bytes_malloc_dup() - * - * Calling amqp_bytes_free on buffers not allocated with one - * of those two functions will result in undefined behavior - * - * \param [in] bytes the buffer to free - * - * \sa amqp_bytes_malloc(), amqp_bytes_malloc_dup() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_bytes_free(amqp_bytes_t bytes); - -/** - * Allocate and initialize a new amqp_connection_state_t object - * - * amqp_connection_state_t objects created with this function - * should be freed with amqp_destroy_connection() - * - * \returns an opaque pointer on success, NULL or 0 on failure. - * - * \sa amqp_destroy_connection() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_connection_state_t AMQP_CALL amqp_new_connection(void); - -/** - * Get the underlying socket descriptor for the connection - * - * \warning Use the socket returned from this function carefully, incorrect use - * of the socket outside of the library will lead to undefined behavior. - * Additionally rabbitmq-c may use the socket differently version-to-version, - * what may work in one version, may break in the next version. Be sure to - * throughly test any applications that use the socket returned by this - * function especially when using a newer version of rabbitmq-c - * - * \param [in] state the connection object - * \returns the socket descriptor if one has been set, -1 otherwise - * - * \sa amqp_tcp_socket_new(), amqp_ssl_socket_new(), amqp_socket_open() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_get_sockfd(amqp_connection_state_t state); - -/** - * Deprecated, use amqp_tcp_socket_new() or amqp_ssl_socket_new() - * - * \deprecated Use amqp_tcp_socket_new() or amqp_ssl_socket_new() - * - * Sets the socket descriptor associated with the connection. The socket - * should be connected to a broker, and should not be read to or written from - * before calling this function. A socket descriptor can be created and opened - * using amqp_open_socket() - * - * \param [in] state the connection object - * \param [in] sockfd the socket - * - * \sa amqp_open_socket(), amqp_tcp_socket_new(), amqp_ssl_socket_new() - * - * \since v0.1 - */ -AMQP_DEPRECATED(AMQP_PUBLIC_FUNCTION void AMQP_CALL - amqp_set_sockfd(amqp_connection_state_t state, int sockfd)); - -/** - * Tune client side parameters - * - * \warning This function may call abort() if the connection is in a certain - * state. As such it should probably not be called code outside the library. - * connection parameters should be specified when calling amqp_login() or - * amqp_login_with_properties() - * - * This function changes channel_max, frame_max, and heartbeat parameters, on - * the client side only. It does not try to renegotiate these parameters with - * the broker. Using this function will lead to unexpected results. - * - * \param [in] state the connection object - * \param [in] channel_max the maximum number of channels. - * The largest this can be is 65535 - * \param [in] frame_max the maximum size of an frame. - * The smallest this can be is 4096 - * The largest this can be is 2147483647 - * Unless you know what you're doing the recommended - * size is 131072 or 128KB - * \param [in] heartbeat the number of seconds between heartbeats - * - * \return AMQP_STATUS_OK on success, an amqp_status_enum value otherwise. - * Possible error codes include: - * - AMQP_STATUS_NO_MEMORY memory allocation failed. - * - AMQP_STATUS_TIMER_FAILURE the underlying system timer indicated it - * failed. - * - * \sa amqp_login(), amqp_login_with_properties() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_tune_connection(amqp_connection_state_t state, - int channel_max, int frame_max, - int heartbeat); - -/** - * Get the maximum number of channels the connection can handle - * - * The maximum number of channels is set when connection negotiation takes - * place in amqp_login() or amqp_login_with_properties(). - * - * \param [in] state the connection object - * \return the maximum number of channels. 0 if there is no limit - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_get_channel_max(amqp_connection_state_t state); - -/** - * Get the maximum size of an frame the connection can handle - * - * The maximum size of an frame is set when connection negotiation takes - * place in amqp_login() or amqp_login_with_properties(). - * - * \param [in] state the connection object - * \return the maximum size of an frame. - * - * \since v0.6 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_get_frame_max(amqp_connection_state_t state); - -/** - * Get the number of seconds between heartbeats of the connection - * - * The number of seconds between heartbeats is set when connection - * negotiation takes place in amqp_login() or amqp_login_with_properties(). - * - * \param [in] state the connection object - * \return the number of seconds between heartbeats. - * - * \since v0.6 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_get_heartbeat(amqp_connection_state_t state); - -/** - * Destroys an amqp_connection_state_t object - * - * Destroys a amqp_connection_state_t object that was created with - * amqp_new_connection(). If the connection with the broker is open, it will be - * implicitly closed with a reply code of 200 (success). Any memory that - * would be freed with amqp_maybe_release_buffers() or - * amqp_maybe_release_buffers_on_channel() will be freed, and use of that - * memory will caused undefined behavior. - * - * \param [in] state the connection object - * \return AMQP_STATUS_OK on success. amqp_status_enum value failure - * - * \sa amqp_new_connection() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_destroy_connection(amqp_connection_state_t state); - -/** - * Process incoming data - * - * \warning This is a low-level function intended for those who want to - * have greater control over input and output over the socket from the - * broker. Correctly using this function requires in-depth knowledge of AMQP - * and rabbitmq-c. - * - * For a given buffer of data received from the broker, decode the first - * frame in the buffer. If more than one frame is contained in the input buffer - * the return value will be less than the received_data size, the caller should - * adjust received_data buffer descriptor to point to the beginning of the - * buffer + the return value. - * - * \param [in] state the connection object - * \param [in] received_data a buffer of data received from the broker. The - * function will return the number of bytes of the buffer it used. The - * function copies these bytes to an internal buffer: this part of the buffer - * may be reused after this function successfully completes. - * \param [in,out] decoded_frame caller should pass in a pointer to an - * amqp_frame_t struct. If there is enough data in received_data for a - * complete frame, decoded_frame->frame_type will be set to something OTHER - * than 0. decoded_frame may contain members pointing to memory owned by - * the state object. This memory can be recycled with - * amqp_maybe_release_buffers() or amqp_maybe_release_buffers_on_channel(). - * \return number of bytes consumed from received_data or 0 if a 0-length - * buffer was passed. A negative return value indicates failure. Possible - * errors: - * - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely - * in an indeterminate state making recovery unlikely. Client should note the - * error and terminate the application - * - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection - * should be shutdown immediately - * - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the - * broker. This is likely a protocol error and the connection should be - * shutdown immediately - * - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class - * was received from the broker. This is likely a protocol error and the - * connection should be shutdown immediately - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_handle_input(amqp_connection_state_t state, - amqp_bytes_t received_data, - amqp_frame_t *decoded_frame); - -/** - * Check to see if connection memory can be released - * - * \deprecated This function is deprecated in favor of - * amqp_maybe_release_buffers() or amqp_maybe_release_buffers_on_channel() - * - * Checks the state of an amqp_connection_state_t object to see if - * amqp_release_buffers() can be called successfully. - * - * \param [in] state the connection object - * \returns TRUE if the buffers can be released FALSE otherwise - * - * \sa amqp_release_buffers() amqp_maybe_release_buffers() - * amqp_maybe_release_buffers_on_channel() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_boolean_t AMQP_CALL amqp_release_buffers_ok(amqp_connection_state_t state); - -/** - * Release amqp_connection_state_t owned memory - * - * \deprecated This function is deprecated in favor of - * amqp_maybe_release_buffers() or amqp_maybe_release_buffers_on_channel() - * - * \warning caller should ensure amqp_release_buffers_ok() returns true before - * calling this function. Failure to do so may result in abort() being called. - * - * Release memory owned by the amqp_connection_state_t for reuse by the - * library. Use of any memory returned by the library before this function is - * called will result in undefined behavior. - * - * \note internally rabbitmq-c tries to reuse memory when possible. As a result - * its possible calling this function may not have a noticeable effect on - * memory usage. - * - * \param [in] state the connection object - * - * \sa amqp_release_buffers_ok() amqp_maybe_release_buffers() - * amqp_maybe_release_buffers_on_channel() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_release_buffers(amqp_connection_state_t state); - -/** - * Release amqp_connection_state_t owned memory - * - * Release memory owned by the amqp_connection_state_t object related to any - * channel, allowing reuse by the library. Use of any memory returned by the - * library before this function is called with result in undefined behavior. - * - * \note internally rabbitmq-c tries to reuse memory when possible. As a result - * its possible calling this function may not have a noticeable effect on - * memory usage. - * - * \param [in] state the connection object - * - * \sa amqp_maybe_release_buffers_on_channel() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_maybe_release_buffers(amqp_connection_state_t state); - -/** - * Release amqp_connection_state_t owned memory related to a channel - * - * Release memory owned by the amqp_connection_state_t object related to the - * specified channel, allowing reuse by the library. Use of any memory returned - * the library for a specific channel will result in undefined behavior. - * - * \note internally rabbitmq-c tries to reuse memory when possible. As a result - * its possible calling this function may not have a noticeable effect on - * memory usage. - * - * \param [in] state the connection object - * \param [in] channel the channel specifier for which memory should be - * released. Note that the library does not care about the state of the - * channel when calling this function - * - * \sa amqp_maybe_release_buffers() - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_maybe_release_buffers_on_channel( - amqp_connection_state_t state, amqp_channel_t channel); - -/** - * Send a frame to the broker - * - * \param [in] state the connection object - * \param [in] frame the frame to send to the broker - * \return AMQP_STATUS_OK on success, an amqp_status_enum value on error. - * Possible error codes: - * - AMQP_STATUS_BAD_AMQP_DATA the serialized form of the method or - * properties was too large to fit in a single AMQP frame, or the - * method contains an invalid value. The frame was not sent. - * - AMQP_STATUS_TABLE_TOO_BIG the serialized form of an amqp_table_t is - * too large to fit in a single AMQP frame. Frame was not sent. - * - AMQP_STATUS_UNKNOWN_METHOD an invalid method type was passed in - * - AMQP_STATUS_UNKNOWN_CLASS an invalid properties type was passed in - * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. The frame - * was sent - * - AMQP_STATUS_SOCKET_ERROR - * - AMQP_STATUS_SSL_ERROR - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_send_frame(amqp_connection_state_t state, - amqp_frame_t const *frame); - -/** - * Compare two table entries - * - * Works just like strcmp(), comparing two the table keys, datatype, then values - * - * \param [in] entry1 the entry on the left - * \param [in] entry2 the entry on the right - * \return 0 if entries are equal, 0 < if left is greater, 0 > if right is - * greater - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_table_entry_cmp(void const *entry1, void const *entry2); - -/** - * Open a socket to a remote host - * - * \deprecated This function is deprecated in favor of amqp_socket_open() - * - * Looks up the hostname, then attempts to open a socket to the host using - * the specified portnumber. It also sets various options on the socket to - * improve performance and correctness. - * - * \param [in] hostname this can be a hostname or IP address. - * Both IPv4 and IPv6 are acceptable - * \param [in] portnumber the port to connect on. RabbitMQ brokers - * listen on port 5672, and 5671 for SSL - * \return a positive value indicates success and is the sockfd. A negative - * value (see amqp_status_enum)is returned on failure. Possible error codes: - * - AMQP_STATUS_TCP_SOCKETLIB_INIT_ERROR Initialization of underlying socket - * library failed. - * - AMQP_STATUS_HOSTNAME_RESOLUTION_FAILED hostname lookup failed. - * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. errno or - * WSAGetLastError() may return more useful information. - * - * \note IPv6 support was added in v0.3 - * - * \sa amqp_socket_open() amqp_set_sockfd() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_open_socket(char const *hostname, int portnumber); - -/** - * Send initial AMQP header to the broker - * - * \warning this is a low level function intended for those who want to - * interact with the broker at a very low level. Use of this function without - * understanding what it does will result in AMQP protocol errors. - * - * This function sends the AMQP protocol header to the broker. - * - * \param [in] state the connection object - * \return AMQP_STATUS_OK on success, a negative value on failure. Possible - * error codes: - * - AMQP_STATUS_CONNECTION_CLOSED the connection to the broker was closed. - * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. It is likely the - * underlying socket has been closed. errno or WSAGetLastError() may provide - * further information. - * - AMQP_STATUS_SSL_ERROR a SSL error occurred. The connection to the broker - * was closed. - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_send_header(amqp_connection_state_t state); - -/** - * Checks to see if there are any incoming frames ready to be read - * - * Checks to see if there are any amqp_frame_t objects buffered by the - * amqp_connection_state_t object. Having one or more frames buffered means - * that amqp_simple_wait_frame() or amqp_simple_wait_frame_noblock() will - * return a frame without potentially blocking on a read() call. - * - * \param [in] state the connection object - * \return TRUE if there are frames enqueued, FALSE otherwise - * - * \sa amqp_simple_wait_frame() amqp_simple_wait_frame_noblock() - * amqp_data_in_buffer() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_boolean_t AMQP_CALL amqp_frames_enqueued(amqp_connection_state_t state); - -/** - * Read a single amqp_frame_t - * - * Waits for the next amqp_frame_t frame to be read from the broker. - * This function has the potential to block for a long time in the case of - * waiting for a basic.deliver method frame from the broker. - * - * The library may buffer frames. When an amqp_connection_state_t object - * has frames buffered calling amqp_simple_wait_frame() will return an - * amqp_frame_t without entering a blocking read(). You can test to see if - * an amqp_connection_state_t object has frames buffered by calling the - * amqp_frames_enqueued() function. - * - * The library has a socket read buffer. When there is data in an - * amqp_connection_state_t read buffer, amqp_simple_wait_frame() may return an - * amqp_frame_t without entering a blocking read(). You can test to see if an - * amqp_connection_state_t object has data in its read buffer by calling the - * amqp_data_in_buffer() function. - * - * \param [in] state the connection object - * \param [out] decoded_frame the frame - * \return AMQP_STATUS_OK on success, an amqp_status_enum value - * is returned otherwise. Possible errors include: - * - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely - * in an indeterminate state making recovery unlikely. Client should note the - * error and terminate the application - * - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection - * should be shutdown immediately - * - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the - * broker. This is likely a protocol error and the connection should be - * shutdown immediately - * - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class - * was received from the broker. This is likely a protocol error and the - * connection should be shutdown immediately - * - AMQP_STATUS_HEARTBEAT_TIMEOUT timed out while waiting for heartbeat - * from the broker. The connection has been closed. - * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. - * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. The connection has - * been closed - * - AMQP_STATUS_SSL_ERROR a SSL socket error occurred. The connection has - * been closed. - * - * \sa amqp_simple_wait_frame_noblock() amqp_frames_enqueued() - * amqp_data_in_buffer() - * - * \note as of v0.4.0 this function will no longer return heartbeat frames - * when enabled by specifying a non-zero heartbeat value in amqp_login(). - * Heartbeating is handled internally by the library. - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_simple_wait_frame(amqp_connection_state_t state, - amqp_frame_t *decoded_frame); - -/** - * Read a single amqp_frame_t with a timeout. - * - * Waits for the next amqp_frame_t frame to be read from the broker, up to - * a timespan specified by tv. The function will return AMQP_STATUS_TIMEOUT - * if the timeout is reached. The tv value is not modified by the function. - * - * If a 0 timeval is specified, the function behaves as if its non-blocking: it - * will test to see if a frame can be read from the broker, and return - * immediately. - * - * If NULL is passed in for tv, the function will behave like - * amqp_simple_wait_frame() and block until a frame is received from the broker - * - * The library may buffer frames. When an amqp_connection_state_t object - * has frames buffered calling amqp_simple_wait_frame_noblock() will return an - * amqp_frame_t without entering a blocking read(). You can test to see if an - * amqp_connection_state_t object has frames buffered by calling the - * amqp_frames_enqueued() function. - * - * The library has a socket read buffer. When there is data in an - * amqp_connection_state_t read buffer, amqp_simple_wait_frame_noblock() may - * return - * an amqp_frame_t without entering a blocking read(). You can test to see if an - * amqp_connection_state_t object has data in its read buffer by calling the - * amqp_data_in_buffer() function. - * - * \note This function does not return heartbeat frames. When enabled, - * heartbeating is handed internally internally by the library. - * - * \param [in,out] state the connection object - * \param [out] decoded_frame the frame - * \param [in] tv the maximum time to wait for a frame to be read. Setting - * tv->tv_sec = 0 and tv->tv_usec = 0 will do a non-blocking read. Specifying - * NULL for tv will make the function block until a frame is read. - * \return AMQP_STATUS_OK on success. An amqp_status_enum value is returned - * otherwise. Possible errors include: - * - AMQP_STATUS_TIMEOUT the timeout was reached while waiting for a frame - * from the broker. - * - AMQP_STATUS_INVALID_PARAMETER the tv parameter contains an invalid value. - * - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely - * in an indeterminate state making recovery unlikely. Client should note the - * error and terminate the application - * - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection - * should be shutdown immediately - * - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the - * broker. This is likely a protocol error and the connection should be - * shutdown immediately - * - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class - * was received from the broker. This is likely a protocol error and the - * connection should be shutdown immediately - * - AMQP_STATUS_HEARTBEAT_TIMEOUT timed out while waiting for heartbeat - * from the broker. The connection has been closed. - * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. - * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. The connection has - * been closed - * - AMQP_STATUS_SSL_ERROR a SSL socket error occurred. The connection has - * been closed. - * - * \sa amqp_simple_wait_frame() amqp_frames_enqueued() amqp_data_in_buffer() - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_simple_wait_frame_noblock(amqp_connection_state_t state, - amqp_frame_t *decoded_frame, - struct timeval *tv); - -/** - * Waits for a specific method from the broker - * - * \warning You probably don't want to use this function. If this function - * doesn't receive exactly the frame requested it closes the whole connection. - * - * Waits for a single method on a channel from the broker. - * If a frame is received that does not match expected_channel - * or expected_method the program will abort - * - * \param [in] state the connection object - * \param [in] expected_channel the channel that the method should be delivered - * on - * \param [in] expected_method the method to wait for - * \param [out] output the method - * \returns AMQP_STATUS_OK on success. An amqp_status_enum value is returned - * otherwise. Possible errors include: - * - AMQP_STATUS_WRONG_METHOD a frame containing the wrong method, wrong frame - * type or wrong channel was received. The connection is closed. - * - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely - * in an indeterminate state making recovery unlikely. Client should note the - * error and terminate the application - * - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection - * should be shutdown immediately - * - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the - * broker. This is likely a protocol error and the connection should be - * shutdown immediately - * - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class - * was received from the broker. This is likely a protocol error and the - * connection should be shutdown immediately - * - AMQP_STATUS_HEARTBEAT_TIMEOUT timed out while waiting for heartbeat - * from the broker. The connection has been closed. - * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. - * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. The connection has - * been closed - * - AMQP_STATUS_SSL_ERROR a SSL socket error occurred. The connection has - * been closed. - * - * \since v0.1 - */ - -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_simple_wait_method(amqp_connection_state_t state, - amqp_channel_t expected_channel, - amqp_method_number_t expected_method, - amqp_method_t *output); - -/** - * Sends a method to the broker - * - * This is a thin wrapper around amqp_send_frame(), providing a way to send - * a method to the broker on a specified channel. - * - * \param [in] state the connection object - * \param [in] channel the channel object - * \param [in] id the method number - * \param [in] decoded the method object - * \returns AMQP_STATUS_OK on success, an amqp_status_enum value otherwise. - * Possible errors include: - * - AMQP_STATUS_BAD_AMQP_DATA the serialized form of the method or - * properties was too large to fit in a single AMQP frame, or the - * method contains an invalid value. The frame was not sent. - * - AMQP_STATUS_TABLE_TOO_BIG the serialized form of an amqp_table_t is - * too large to fit in a single AMQP frame. Frame was not sent. - * - AMQP_STATUS_UNKNOWN_METHOD an invalid method type was passed in - * - AMQP_STATUS_UNKNOWN_CLASS an invalid properties type was passed in - * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. The frame - * was sent - * - AMQP_STATUS_SOCKET_ERROR - * - AMQP_STATUS_SSL_ERROR - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_send_method(amqp_connection_state_t state, - amqp_channel_t channel, amqp_method_number_t id, - void *decoded); - -/** - * Sends a method to the broker and waits for a method response - * - * \param [in] state the connection object - * \param [in] channel the channel object - * \param [in] request_id the method number of the request - * \param [in] expected_reply_ids a 0 terminated array of expected response - * method numbers - * \param [in] decoded_request_method the method to be sent to the broker - * \return a amqp_rpc_reply_t: - * - r.reply_type == AMQP_RESPONSE_NORMAL. RPC completed successfully - * - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an - * exception: - * - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception - * occurred, cast r.reply.decoded to amqp_channel_close_t* to see details - * of the exception. The client should amqp_send_method() a - * amqp_channel_close_ok_t. The channel must be re-opened before it - * can be used again. Any resources associated with the channel - * (auto-delete exchanges, auto-delete queues, consumers) are invalid - * and must be recreated before attempting to use them again. - * - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception - * occurred, cast r.reply.decoded to amqp_connection_close_t* to see - * details of the exception. The client amqp_send_method() a - * amqp_connection_close_ok_t and disconnect from the broker. - * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. An exception occurred - * within the library. Examine r.library_error and compare it against - * amqp_status_enum values to determine the error. - * - * \sa amqp_simple_rpc_decoded() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_simple_rpc( - amqp_connection_state_t state, amqp_channel_t channel, - amqp_method_number_t request_id, amqp_method_number_t *expected_reply_ids, - void *decoded_request_method); - -/** - * Sends a method to the broker and waits for a method response - * - * \param [in] state the connection object - * \param [in] channel the channel object - * \param [in] request_id the method number of the request - * \param [in] reply_id the method number expected in response - * \param [in] decoded_request_method the request method - * \return a pointer to the method returned from the broker, or NULL on error. - * On error amqp_get_rpc_reply() will return an amqp_rpc_reply_t with - * details on the error that occurred. - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void *AMQP_CALL amqp_simple_rpc_decoded(amqp_connection_state_t state, - amqp_channel_t channel, - amqp_method_number_t request_id, - amqp_method_number_t reply_id, - void *decoded_request_method); - -/** - * Get the last global amqp_rpc_reply - * - * The API methods corresponding to most synchronous AMQP methods - * return a pointer to the decoded method result. Upon error, they - * return NULL, and we need some way of discovering what, if anything, - * went wrong. amqp_get_rpc_reply() returns the most recent - * amqp_rpc_reply_t instance corresponding to such an API operation - * for the given connection. - * - * Only use it for operations that do not themselves return - * amqp_rpc_reply_t; operations that do return amqp_rpc_reply_t - * generally do NOT update this per-connection-global amqp_rpc_reply_t - * instance. - * - * \param [in] state the connection object - * \return the most recent amqp_rpc_reply_t: - * - r.reply_type == AMQP_RESPONSE_NORMAL. RPC completed successfully - * - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an - * exception: - * - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception - * occurred, cast r.reply.decoded to amqp_channel_close_t* to see details - * of the exception. The client should amqp_send_method() a - * amqp_channel_close_ok_t. The channel must be re-opened before it - * can be used again. Any resources associated with the channel - * (auto-delete exchanges, auto-delete queues, consumers) are invalid - * and must be recreated before attempting to use them again. - * - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception - * occurred, cast r.reply.decoded to amqp_connection_close_t* to see - * details of the exception. The client amqp_send_method() a - * amqp_connection_close_ok_t and disconnect from the broker. - * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. An exception occurred - * within the library. Examine r.library_error and compare it against - * amqp_status_enum values to determine the error. - * - * \sa amqp_simple_rpc_decoded() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_get_rpc_reply(amqp_connection_state_t state); - -/** - * Login to the broker - * - * After using amqp_open_socket and amqp_set_sockfd, call - * amqp_login to complete connecting to the broker - * - * \param [in] state the connection object - * \param [in] vhost the virtual host to connect to on the broker. The default - * on most brokers is "/" - * \param [in] channel_max the limit for number of channels for the connection. - * 0 means no limit, and is a good default - * (AMQP_DEFAULT_MAX_CHANNELS) - * Note that the maximum number of channels the protocol supports - * is 65535 (2^16, with the 0-channel reserved). The server can - * set a lower channel_max and then the client will use the lowest - * of the two - * \param [in] frame_max the maximum size of an AMQP frame on the wire to - * request of the broker for this connection. 4096 is the minimum - * size, 2^31-1 is the maximum, a good default is 131072 (128KB), - * or AMQP_DEFAULT_FRAME_SIZE - * \param [in] heartbeat the number of seconds between heartbeat frames to - * request of the broker. A value of 0 disables heartbeats. - * Note rabbitmq-c only has partial support for heartbeats, as of - * v0.4.0 they are only serviced during amqp_basic_publish() and - * amqp_simple_wait_frame()/amqp_simple_wait_frame_noblock() - * \param [in] sasl_method the SASL method to authenticate with the broker. - * followed by the authentication information. The following SASL - * methods are implemented: - * - AMQP_SASL_METHOD_PLAIN, the AMQP_SASL_METHOD_PLAIN argument - * should be followed by two arguments in this order: - * const char* username, and const char* password. - * - AMQP_SASL_METHOD_EXTERNAL, the AMQP_SASL_METHOD_EXTERNAL - * argument should be followed one argument: - * const char* identity. - * \return amqp_rpc_reply_t indicating success or failure. - * - r.reply_type == AMQP_RESPONSE_NORMAL. Login completed successfully - * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. In most cases errors - * from the broker when logging in will be represented by the broker closing - * the socket. In this case r.library_error will be set to - * AMQP_STATUS_CONNECTION_CLOSED. This error can represent a number of - * error conditions including: invalid vhost, authentication failure. - * - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an - * exception: - * - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception - * occurred, cast r.reply.decoded to amqp_channel_close_t* to see details - * of the exception. The client should amqp_send_method() a - * amqp_channel_close_ok_t. The channel must be re-opened before it - * can be used again. Any resources associated with the channel - * (auto-delete exchanges, auto-delete queues, consumers) are invalid - * and must be recreated before attempting to use them again. - * - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception - * occurred, cast r.reply.decoded to amqp_connection_close_t* to see - * details of the exception. The client amqp_send_method() a - * amqp_connection_close_ok_t and disconnect from the broker. - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_login(amqp_connection_state_t state, - char const *vhost, int channel_max, - int frame_max, int heartbeat, - amqp_sasl_method_enum sasl_method, ...); - -/** - * Login to the broker passing a properties table - * - * This function is similar to amqp_login() and differs in that it provides a - * way to pass client properties to the broker. This is commonly used to - * negotiate newer protocol features as they are supported by the broker. - * - * \param [in] state the connection object - * \param [in] vhost the virtual host to connect to on the broker. The default - * on most brokers is "/" - * \param [in] channel_max the limit for the number of channels for the - * connection. - * 0 means no limit, and is a good default - * (AMQP_DEFAULT_MAX_CHANNELS) - * Note that the maximum number of channels the protocol supports - * is 65535 (2^16, with the 0-channel reserved). The server can - * set a lower channel_max and then the client will use the lowest - * of the two - * \param [in] frame_max the maximum size of an AMQP frame ont he wire to - * request of the broker for this connection. 4096 is the minimum - * size, 2^31-1 is the maximum, a good default is 131072 (128KB), - * or AMQP_DEFAULT_FRAME_SIZE - * \param [in] heartbeat the number of seconds between heartbeat frame to - * request of the broker. A value of 0 disables heartbeats. - * Note rabbitmq-c only has partial support for hearts, as of - * v0.4.0 heartbeats are only serviced during amqp_basic_publish(), - * and amqp_simple_wait_frame()/amqp_simple_wait_frame_noblock() - * \param [in] properties a table of properties to send the broker. - * \param [in] sasl_method the SASL method to authenticate with the broker - * followed by the authentication information. The following SASL - * methods are implemented: - * - AMQP_SASL_METHOD_PLAIN, the AMQP_SASL_METHOD_PLAIN argument - * should be followed by two arguments in this order: - * const char* username, and const char* password. - * - AMQP_SASL_METHOD_EXTERNAL, the AMQP_SASL_METHOD_EXTERNAL - * argument should be followed one argument: - * const char* identity. - * \return amqp_rpc_reply_t indicating success or failure. - * - r.reply_type == AMQP_RESPONSE_NORMAL. Login completed successfully - * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. In most cases errors - * from the broker when logging in will be represented by the broker closing - * the socket. In this case r.library_error will be set to - * AMQP_STATUS_CONNECTION_CLOSED. This error can represent a number of - * error conditions including: invalid vhost, authentication failure. - * - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an - * exception: - * - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception - * occurred, cast r.reply.decoded to amqp_channel_close_t* to see details - * of the exception. The client should amqp_send_method() a - * amqp_channel_close_ok_t. The channel must be re-opened before it - * can be used again. Any resources associated with the channel - * (auto-delete exchanges, auto-delete queues, consumers) are invalid - * and must be recreated before attempting to use them again. - * - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception - * occurred, cast r.reply.decoded to amqp_connection_close_t* to see - * details of the exception. The client amqp_send_method() a - * amqp_connection_close_ok_t and disconnect from the broker. - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_login_with_properties( - amqp_connection_state_t state, char const *vhost, int channel_max, - int frame_max, int heartbeat, const amqp_table_t *properties, - amqp_sasl_method_enum sasl_method, ...); - -struct amqp_basic_properties_t_; - -/** - * Publish a message to the broker - * - * Publish a message on an exchange with a routing key. - * - * Note that at the AMQ protocol level basic.publish is an async method: - * this means error conditions that occur on the broker (such as publishing to - * a non-existent exchange) will not be reflected in the return value of this - * function. - * - * \param [in] state the connection object - * \param [in] channel the channel identifier - * \param [in] exchange the exchange on the broker to publish to - * \param [in] routing_key the routing key to use when publishing the message - * \param [in] mandatory indicate to the broker that the message MUST be routed - * to a queue. If the broker cannot do this it should respond with - * a basic.return method. - * \param [in] immediate indicate to the broker that the message MUST be - * delivered to a consumer immediately. If the broker cannot do this - * it should respond with a basic.return method. - * \param [in] properties the properties associated with the message - * \param [in] body the message body - * \return AMQP_STATUS_OK on success, amqp_status_enum value on failure. Note - * that basic.publish is an async method, the return value from this - * function only indicates that the message data was successfully - * transmitted to the broker. It does not indicate failures that occur - * on the broker, such as publishing to a non-existent exchange. - * Possible error values: - * - AMQP_STATUS_TIMER_FAILURE: system timer facility returned an error - * the message was not sent. - * - AMQP_STATUS_HEARTBEAT_TIMEOUT: connection timed out waiting for a - * heartbeat from the broker. The message was not sent. - * - AMQP_STATUS_NO_MEMORY: memory allocation failed. The message was - * not sent. - * - AMQP_STATUS_TABLE_TOO_BIG: a table in the properties was too large - * to fit in a single frame. Message was not sent. - * - AMQP_STATUS_CONNECTION_CLOSED: the connection was closed. - * - AMQP_STATUS_SSL_ERROR: a SSL error occurred. - * - AMQP_STATUS_TCP_ERROR: a TCP error occurred. errno or - * WSAGetLastError() may provide more information - * - * Note: this function does heartbeat processing as of v0.4.0 - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_basic_publish( - amqp_connection_state_t state, amqp_channel_t channel, - amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_boolean_t mandatory, - amqp_boolean_t immediate, struct amqp_basic_properties_t_ const *properties, - amqp_bytes_t body); - -/** - * Closes an channel - * - * \param [in] state the connection object - * \param [in] channel the channel identifier - * \param [in] code the reason for closing the channel, AMQP_REPLY_SUCCESS is a - * good default - * \return amqp_rpc_reply_t indicating success or failure - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_channel_close(amqp_connection_state_t state, - amqp_channel_t channel, int code); - -/** - * Closes the entire connection - * - * Implicitly closes all channels and informs the broker the connection - * is being closed, after receiving acknowledgment from the broker it closes - * the socket. - * - * \param [in] state the connection object - * \param [in] code the reason code for closing the connection. - * AMQP_REPLY_SUCCESS is a good default. - * \return amqp_rpc_reply_t indicating the result - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_connection_close(amqp_connection_state_t state, - int code); - -/** - * Acknowledges a message - * - * Does a basic.ack on a received message - * - * \param [in] state the connection object - * \param [in] channel the channel identifier - * \param [in] delivery_tag the delivery tag of the message to be ack'd - * \param [in] multiple if true, ack all messages up to this delivery tag, if - * false ack only this delivery tag - * \return 0 on success, 0 > on failing to send the ack to the broker. - * this will not indicate failure if something goes wrong on the - * broker - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_basic_ack(amqp_connection_state_t state, - amqp_channel_t channel, uint64_t delivery_tag, - amqp_boolean_t multiple); - -/** - * Do a basic.get - * - * Synchonously polls the broker for a message in a queue, and - * retrieves the message if a message is in the queue. - * - * \param [in] state the connection object - * \param [in] channel the channel identifier to use - * \param [in] queue the queue name to retrieve from - * \param [in] no_ack if true the message is automatically ack'ed - * if false amqp_basic_ack should be called once the message - * retrieved has been processed - * \return amqp_rpc_reply indicating success or failure - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_basic_get(amqp_connection_state_t state, - amqp_channel_t channel, - amqp_bytes_t queue, - amqp_boolean_t no_ack); - -/** - * Do a basic.reject - * - * Actively reject a message that has been delivered - * - * \param [in] state the connection object - * \param [in] channel the channel identifier - * \param [in] delivery_tag the delivery tag of the message to reject - * \param [in] requeue indicate to the broker whether it should requeue the - * message or just discard it. - * \return 0 on success, 0 > on failing to send the reject method to the broker. - * This will not indicate failure if something goes wrong on the - * broker. - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_basic_reject(amqp_connection_state_t state, - amqp_channel_t channel, uint64_t delivery_tag, - amqp_boolean_t requeue); - -/** - * Do a basic.nack - * - * Actively reject a message, this has the same effect as amqp_basic_reject() - * however, amqp_basic_nack() can negatively acknowledge multiple messages with - * one call much like amqp_basic_ack() can acknowledge mutliple messages with - * one call. - * - * \param [in] state the connection object - * \param [in] channel the channel identifier - * \param [in] delivery_tag the delivery tag of the message to reject - * \param [in] multiple if set to 1 negatively acknowledge all unacknowledged - * messages on this channel. - * \param [in] requeue indicate to the broker whether it should requeue the - * message or dead-letter it. - * \return AMQP_STATUS_OK on success, an amqp_status_enum value otherwise. - * - * \since v0.5.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_basic_nack(amqp_connection_state_t state, - amqp_channel_t channel, uint64_t delivery_tag, - amqp_boolean_t multiple, amqp_boolean_t requeue); -/** - * Check to see if there is data left in the receive buffer - * - * Can be used to see if there is data still in the buffer, if so - * calling amqp_simple_wait_frame will not immediately enter a - * blocking read. - * - * \param [in] state the connection object - * \return true if there is data in the recieve buffer, false otherwise - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_boolean_t AMQP_CALL amqp_data_in_buffer(amqp_connection_state_t state); - -/** - * Get the error string for the given error code. - * - * \deprecated This function has been deprecated in favor of - * \ref amqp_error_string2() which returns statically allocated - * string which do not need to be freed by the caller. - * - * The returned string resides on the heap; the caller is responsible - * for freeing it. - * - * \param [in] err return error code - * \return the error string - * - * \since v0.1 - */ -AMQP_DEPRECATED( - AMQP_PUBLIC_FUNCTION char *AMQP_CALL amqp_error_string(int err)); - -/** - * Get the error string for the given error code. - * - * Get an error string associated with an error code. The string is statically - * allocated and does not need to be freed - * - * \param [in] err the error code - * \return the error string - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -const char *AMQP_CALL amqp_error_string2(int err); - -/** - * Deserialize an amqp_table_t from AMQP wireformat - * - * This is an internal function and is not typically used by - * client applications - * - * \param [in] encoded the buffer containing the serialized data - * \param [in] pool memory pool used to allocate the table entries from - * \param [in] output the amqp_table_t structure to fill in. Any existing - * entries will be erased - * \param [in,out] offset The offset into the encoded buffer to start - * reading the serialized table. It will be updated - * by this function to end of the table - * \return AMQP_STATUS_OK on success, an amqp_status_enum value on failure - * Possible error codes: - * - AMQP_STATUS_NO_MEMORY out of memory - * - AMQP_STATUS_BAD_AMQP_DATA invalid wireformat - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_decode_table(amqp_bytes_t encoded, amqp_pool_t *pool, - amqp_table_t *output, size_t *offset); - -/** - * Serializes an amqp_table_t to the AMQP wireformat - * - * This is an internal function and is not typically used by - * client applications - * - * \param [in] encoded the buffer where to serialize the table to - * \param [in] input the amqp_table_t to serialize - * \param [in,out] offset The offset into the encoded buffer to start - * writing the serialized table. It will be updated - * by this function to where writing left off - * \return AMQP_STATUS_OK on success, an amqp_status_enum value on failure - * Possible error codes: - * - AMQP_STATUS_TABLE_TOO_BIG the serialized form is too large for the - * buffer - * - AMQP_STATUS_BAD_AMQP_DATA invalid table - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_encode_table(amqp_bytes_t encoded, amqp_table_t *input, - size_t *offset); - -/** - * Create a deep-copy of an amqp_table_t object - * - * Creates a deep-copy of an amqp_table_t object, using the provided pool - * object to allocate the necessary memory. This memory can be freed later by - * call recycle_amqp_pool(), or empty_amqp_pool() - * - * \param [in] original the table to copy - * \param [in,out] clone the table to copy to - * \param [in] pool the initialized memory pool to do allocations for the table - * from - * \return AMQP_STATUS_OK on success, amqp_status_enum value on failure. - * Possible error values: - * - AMQP_STATUS_NO_MEMORY - memory allocation failure. - * - AMQP_STATUS_INVALID_PARAMETER - invalid table (e.g., no key name) - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_table_clone(const amqp_table_t *original, - amqp_table_t *clone, amqp_pool_t *pool); - -/** - * A message object - * - * \since v0.4.0 - */ -typedef struct amqp_message_t_ { - amqp_basic_properties_t properties; /**< message properties */ - amqp_bytes_t body; /**< message body */ - amqp_pool_t pool; /**< pool used to allocate properties */ -} amqp_message_t; - -/** - * Reads the next message on a channel - * - * Reads a complete message (header + body) on a specified channel. This - * function is intended to be used with amqp_basic_get() or when an - * AMQP_BASIC_DELIVERY_METHOD method is received. - * - * \param [in,out] state the connection object - * \param [in] channel the channel on which to read the message from - * \param [in,out] message a pointer to a amqp_message_t object. Caller should - * call amqp_message_destroy() when it is done using the - * fields in the message object. The caller is responsible for - * allocating/destroying the amqp_message_t object itself. - * \param [in] flags pass in 0. Currently unused. - * \returns a amqp_rpc_reply_t object. ret.reply_type == AMQP_RESPONSE_NORMAL on - * success. - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_read_message(amqp_connection_state_t state, - amqp_channel_t channel, - amqp_message_t *message, - int flags); - -/** - * Frees memory associated with a amqp_message_t allocated in amqp_read_message - * - * \param [in] message - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_destroy_message(amqp_message_t *message); - -/** - * Envelope object - * - * \since v0.4.0 - */ -typedef struct amqp_envelope_t_ { - amqp_channel_t channel; /**< channel message was delivered on */ - amqp_bytes_t - consumer_tag; /**< the consumer tag the message was delivered to */ - uint64_t delivery_tag; /**< the messages delivery tag */ - amqp_boolean_t redelivered; /**< flag indicating whether this message is being - redelivered */ - amqp_bytes_t exchange; /**< exchange this message was published to */ - amqp_bytes_t - routing_key; /**< the routing key this message was published with */ - amqp_message_t message; /**< the message */ -} amqp_envelope_t; - -/** - * Wait for and consume a message - * - * Waits for a basic.deliver method on any channel, upon receipt of - * basic.deliver it reads that message, and returns. If any other method is - * received before basic.deliver, this function will return an amqp_rpc_reply_t - * with ret.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION, and - * ret.library_error == AMQP_STATUS_UNEXPECTED_STATE. The caller should then - * call amqp_simple_wait_frame() to read this frame and take appropriate action. - * - * This function should be used after starting a consumer with the - * amqp_basic_consume() function - * - * \param [in,out] state the connection object - * \param [in,out] envelope a pointer to a amqp_envelope_t object. Caller - * should call #amqp_destroy_envelope() when it is done using - * the fields in the envelope object. The caller is responsible - * for allocating/destroying the amqp_envelope_t object itself. - * \param [in] timeout a timeout to wait for a message delivery. Passing in - * NULL will result in blocking behavior. - * \param [in] flags pass in 0. Currently unused. - * \returns a amqp_rpc_reply_t object. ret.reply_type == AMQP_RESPONSE_NORMAL - * on success. If ret.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION, - * and ret.library_error == AMQP_STATUS_UNEXPECTED_STATE, a frame other - * than AMQP_BASIC_DELIVER_METHOD was received, the caller should call - * amqp_simple_wait_frame() to read this frame and take appropriate - * action. - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_consume_message(amqp_connection_state_t state, - amqp_envelope_t *envelope, - struct timeval *timeout, - int flags); - -/** - * Frees memory associated with a amqp_envelope_t allocated in - * amqp_consume_message() - * - * \param [in] envelope - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_destroy_envelope(amqp_envelope_t *envelope); - -/** - * Parameters used to connect to the RabbitMQ broker - * - * \since v0.2 - */ -struct amqp_connection_info { - char *user; /**< the username to authenticate with the broker, default on most - broker is 'guest' */ - char *password; /**< the password to authenticate with the broker, default on - most brokers is 'guest' */ - char *host; /**< the hostname of the broker */ - char *vhost; /**< the virtual host on the broker to connect to, a good default - is "/" */ - int port; /**< the port that the broker is listening on, default on most - brokers is 5672 */ - amqp_boolean_t ssl; -}; - -/** - * Initialze an amqp_connection_info to default values - * - * The default values are: - * - user: "guest" - * - password: "guest" - * - host: "localhost" - * - vhost: "/" - * - port: 5672 - * - * \param [out] parsed the connection info to set defaults on - * - * \since v0.2 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL - amqp_default_connection_info(struct amqp_connection_info *parsed); - -/** - * Parse a connection URL - * - * An amqp connection url takes the form: - * - * amqp://[$USERNAME[:$PASSWORD]\@]$HOST[:$PORT]/[$VHOST] - * - * Examples: - * amqp://guest:guest\@localhost:5672// - * amqp://guest:guest\@localhost/myvhost - * - * Any missing parts of the URL will be set to the defaults specified in - * amqp_default_connection_info. For amqps: URLs the default port will be set - * to 5671 instead of 5672 for non-SSL URLs. - * - * \note This function modifies url parameter. - * - * \param [in] url URI to parse, note that this parameter is modified by the - * function. - * \param [out] parsed the connection info gleaned from the URI. The char* - * members will point to parts of the url input parameter. - * Memory management will depend on how the url is allocated. - * \returns AMQP_STATUS_OK on success, AMQP_STATUS_BAD_URL on failure - * - * \since v0.2 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_parse_url(char *url, struct amqp_connection_info *parsed); - -/* socket API */ - -/** - * Open a socket connection. - * - * This function opens a socket connection returned from amqp_tcp_socket_new() - * or amqp_ssl_socket_new(). This function should be called after setting - * socket options and prior to assigning the socket to an AMQP connection with - * amqp_set_socket(). - * - * \param [in,out] self A socket object. - * \param [in] host Connect to this host. - * \param [in] port Connect on this remote port. - * - * \return AMQP_STATUS_OK on success, an amqp_status_enum on failure - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_socket_open(amqp_socket_t *self, const char *host, int port); - -/** - * Open a socket connection. - * - * This function opens a socket connection returned from amqp_tcp_socket_new() - * or amqp_ssl_socket_new(). This function should be called after setting - * socket options and prior to assigning the socket to an AMQP connection with - * amqp_set_socket(). - * - * \param [in,out] self A socket object. - * \param [in] host Connect to this host. - * \param [in] port Connect on this remote port. - * \param [in] timeout Max allowed time to spent on opening. If NULL - run in - * blocking mode - * - * \return AMQP_STATUS_OK on success, an amqp_status_enum on failure. - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_socket_open_noblock(amqp_socket_t *self, const char *host, - int port, struct timeval *timeout); - -/** - * Get the socket descriptor in use by a socket object. - * - * Retrieve the underlying socket descriptor. This function can be used to - * perform low-level socket operations that aren't supported by the socket - * interface. Use with caution! - * - * \param [in,out] self A socket object. - * - * \return The underlying socket descriptor, or -1 if there is no socket - * descriptor associated with - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_socket_get_sockfd(amqp_socket_t *self); - -/** - * Get the socket object associated with a amqp_connection_state_t - * - * \param [in] state the connection object to get the socket from - * \return a pointer to the socket object, or NULL if one has not been assigned - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_socket_t *AMQP_CALL amqp_get_socket(amqp_connection_state_t state); - -/** - * Get the broker properties table - * - * \param [in] state the connection object - * \return a pointer to an amqp_table_t containing the properties advertised - * by the broker on connection. The connection object owns the table, it - * should not be modified. - * - * \since v0.5.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_table_t *AMQP_CALL - amqp_get_server_properties(amqp_connection_state_t state); - -/** - * Get the client properties table - * - * Get the properties that were passed to the broker on connection. - * - * \param [in] state the connection object - * \return a pointer to an amqp_table_t containing the properties advertised - * by the client on connection. The connection object owns the table, it - * should not be modified. - * - * \since v0.7.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_table_t *AMQP_CALL - amqp_get_client_properties(amqp_connection_state_t state); - -/** - * Get the login handshake timeout. - * - * amqp_login and amqp_login_with_properties perform the login handshake with - * the broker. This function returns the timeout associated with completing - * this operation from the client side. This value can be set by using the - * amqp_set_handshake_timeout. - * - * Note that the RabbitMQ broker has configurable timeout for completing the - * login handshake, the default is 10 seconds. rabbitmq-c has a default of 12 - * seconds. - * - * \param [in] state the connection object - * \return a struct timeval representing the current login timeout for the state - * object. A NULL value represents an infinite timeout. The memory returned is - * owned by the connection object. - * - * \since v0.9.0 - */ -AMQP_PUBLIC_FUNCTION -struct timeval *AMQP_CALL - amqp_get_handshake_timeout(amqp_connection_state_t state); - -/** - * Set the login handshake timeout. - * - * amqp_login and amqp_login_with_properties perform the login handshake with - * the broker. This function sets the timeout associated with completing this - * operation from the client side. - * - * The timeout must be set before amqp_login or amqp_login_with_properties is - * called to change from the default timeout. - * - * Note that the RabbitMQ broker has a configurable timeout for completing the - * login handshake, the default is 10 seconds. rabbitmq-c has a default of 12 - * seconds. - * - * \param [in] state the connection object - * \param [in] timeout a struct timeval* representing new login timeout for the - * state object. NULL represents an infinite timeout. The value of timeout is - * copied internally, the caller is responsible for ownership of the passed in - * pointer, it does not need to remain valid after this function is called. - * \return AMQP_STATUS_OK on success. - * - * \since v0.9.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_set_handshake_timeout(amqp_connection_state_t state, - struct timeval *timeout); - -/** - * Get the RPC timeout - * - * Gets the timeout for any RPC-style AMQP command (e.g., amqp_queue_declare). - * This timeout may be changed at any time by calling \amqp_set_rpc_timeout - * function with a new timeout. The timeout applies individually to each RPC - * that is made. - * - * The default value is NULL, or an infinite timeout. - * - * When an RPC times out, the function will return an error AMQP_STATUS_TIMEOUT, - * and the connection will be closed. - * - *\warning RPC-timeouts are an advanced feature intended to be used to detect - * dead connections quickly when the rabbitmq-c implementation of heartbeats - * does not work. Do not use RPC timeouts unless you understand the implications - * of doing so. - * - * \param [in] state the connection object - * \return a struct timeval representing the current RPC timeout for the state - * object. A NULL value represents an infinite timeout. The memory returned is - * owned by the connection object. - * - * \since v0.9.0 - */ -AMQP_PUBLIC_FUNCTION -struct timeval *AMQP_CALL amqp_get_rpc_timeout(amqp_connection_state_t state); - -/** - * Set the RPC timeout - * - * Sets the timeout for any RPC-style AMQP command (e.g., amqp_queue_declare). - * This timeout may be changed at any time by calling this function with a new - * timeout. The timeout applies individually to each RPC that is made. - * - * The default value is NULL, or an infinite timeout. - * - * When an RPC times out, the function will return an error AMQP_STATUS_TIMEOUT, - * and the connection will be closed. - * - *\warning RPC-timeouts are an advanced feature intended to be used to detect - * dead connections quickly when the rabbitmq-c implementation of heartbeats - * does not work. Do not use RPC timeouts unless you understand the implications - * of doing so. - * - * \param [in] state the connection object - * \param [in] timeout a struct timeval* representing new RPC timeout for the - * state object. NULL represents an infinite timeout. The value of timeout is - * copied internally, the caller is responsible for ownership of the passed - * pointer, it does not need to remain valid after this function is called. - * \return AMQP_STATUS_SUCCESS on success. - * - * \since v0.9.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_set_rpc_timeout(amqp_connection_state_t state, - struct timeval *timeout); - -AMQP_END_DECLS - -#endif /* AMQP_H */ diff --git a/ext/librabbitmq/centos_x64/include/amqp_framing.h b/ext/librabbitmq/centos_x64/include/amqp_framing.h deleted file mode 100644 index fb20acc1f..000000000 --- a/ext/librabbitmq/centos_x64/include/amqp_framing.h +++ /dev/null @@ -1,1144 +0,0 @@ -/* Generated code. Do not edit. Edit and re-run codegen.py instead. - * - * ***** BEGIN LICENSE BLOCK ***** - * Version: MIT - * - * Portions created by Alan Antonuk are Copyright (c) 2012-2013 - * Alan Antonuk. All Rights Reserved. - * - * Portions created by VMware are Copyright (c) 2007-2012 VMware, Inc. - * All Rights Reserved. - * - * Portions created by Tony Garnock-Jones are Copyright (c) 2009-2010 - * VMware, Inc. and Tony Garnock-Jones. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * ***** END LICENSE BLOCK ***** - */ - -/** @file amqp_framing.h */ -#ifndef AMQP_FRAMING_H -#define AMQP_FRAMING_H - -#include - -AMQP_BEGIN_DECLS - -#define AMQP_PROTOCOL_VERSION_MAJOR 0 /**< AMQP protocol version major */ -#define AMQP_PROTOCOL_VERSION_MINOR 9 /**< AMQP protocol version minor */ -#define AMQP_PROTOCOL_VERSION_REVISION \ - 1 /**< AMQP protocol version revision \ - */ -#define AMQP_PROTOCOL_PORT 5672 /**< Default AMQP Port */ -#define AMQP_FRAME_METHOD 1 /**< Constant: FRAME-METHOD */ -#define AMQP_FRAME_HEADER 2 /**< Constant: FRAME-HEADER */ -#define AMQP_FRAME_BODY 3 /**< Constant: FRAME-BODY */ -#define AMQP_FRAME_HEARTBEAT 8 /**< Constant: FRAME-HEARTBEAT */ -#define AMQP_FRAME_MIN_SIZE 4096 /**< Constant: FRAME-MIN-SIZE */ -#define AMQP_FRAME_END 206 /**< Constant: FRAME-END */ -#define AMQP_REPLY_SUCCESS 200 /**< Constant: REPLY-SUCCESS */ -#define AMQP_CONTENT_TOO_LARGE 311 /**< Constant: CONTENT-TOO-LARGE */ -#define AMQP_NO_ROUTE 312 /**< Constant: NO-ROUTE */ -#define AMQP_NO_CONSUMERS 313 /**< Constant: NO-CONSUMERS */ -#define AMQP_ACCESS_REFUSED 403 /**< Constant: ACCESS-REFUSED */ -#define AMQP_NOT_FOUND 404 /**< Constant: NOT-FOUND */ -#define AMQP_RESOURCE_LOCKED 405 /**< Constant: RESOURCE-LOCKED */ -#define AMQP_PRECONDITION_FAILED 406 /**< Constant: PRECONDITION-FAILED */ -#define AMQP_CONNECTION_FORCED 320 /**< Constant: CONNECTION-FORCED */ -#define AMQP_INVALID_PATH 402 /**< Constant: INVALID-PATH */ -#define AMQP_FRAME_ERROR 501 /**< Constant: FRAME-ERROR */ -#define AMQP_SYNTAX_ERROR 502 /**< Constant: SYNTAX-ERROR */ -#define AMQP_COMMAND_INVALID 503 /**< Constant: COMMAND-INVALID */ -#define AMQP_CHANNEL_ERROR 504 /**< Constant: CHANNEL-ERROR */ -#define AMQP_UNEXPECTED_FRAME 505 /**< Constant: UNEXPECTED-FRAME */ -#define AMQP_RESOURCE_ERROR 506 /**< Constant: RESOURCE-ERROR */ -#define AMQP_NOT_ALLOWED 530 /**< Constant: NOT-ALLOWED */ -#define AMQP_NOT_IMPLEMENTED 540 /**< Constant: NOT-IMPLEMENTED */ -#define AMQP_INTERNAL_ERROR 541 /**< Constant: INTERNAL-ERROR */ - -/* Function prototypes. */ - -/** - * Get constant name string from constant - * - * @param [in] constantNumber constant to get the name of - * @returns string describing the constant. String is managed by - * the library and should not be free()'d by the program - */ -AMQP_PUBLIC_FUNCTION -char const *AMQP_CALL amqp_constant_name(int constantNumber); - -/** - * Checks to see if a constant is a hard error - * - * A hard error occurs when something severe enough - * happens that the connection must be closed. - * - * @param [in] constantNumber the error constant - * @returns true if its a hard error, false otherwise - */ -AMQP_PUBLIC_FUNCTION -amqp_boolean_t AMQP_CALL amqp_constant_is_hard_error(int constantNumber); - -/** - * Get method name string from method number - * - * @param [in] methodNumber the method number - * @returns method name string. String is managed by the library - * and should not be freed()'d by the program - */ -AMQP_PUBLIC_FUNCTION -char const *AMQP_CALL amqp_method_name(amqp_method_number_t methodNumber); - -/** - * Check whether a method has content - * - * A method that has content will receive the method frame - * a properties frame, then 1 to N body frames - * - * @param [in] methodNumber the method number - * @returns true if method has content, false otherwise - */ -AMQP_PUBLIC_FUNCTION -amqp_boolean_t AMQP_CALL - amqp_method_has_content(amqp_method_number_t methodNumber); - -/** - * Decodes a method from AMQP wireformat - * - * @param [in] methodNumber the method number for the decoded parameter - * @param [in] pool the memory pool to allocate the decoded method from - * @param [in] encoded the encoded byte string buffer - * @param [out] decoded pointer to the decoded method struct - * @returns 0 on success, an error code otherwise - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_decode_method(amqp_method_number_t methodNumber, - amqp_pool_t *pool, amqp_bytes_t encoded, - void **decoded); - -/** - * Decodes a header frame properties structure from AMQP wireformat - * - * @param [in] class_id the class id for the decoded parameter - * @param [in] pool the memory pool to allocate the decoded properties from - * @param [in] encoded the encoded byte string buffer - * @param [out] decoded pointer to the decoded properties struct - * @returns 0 on success, an error code otherwise - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_decode_properties(uint16_t class_id, amqp_pool_t *pool, - amqp_bytes_t encoded, void **decoded); - -/** - * Encodes a method structure in AMQP wireformat - * - * @param [in] methodNumber the method number for the decoded parameter - * @param [in] decoded the method structure (e.g., amqp_connection_start_t) - * @param [in] encoded an allocated byte buffer for the encoded method - * structure to be written to. If the buffer isn't large enough - * to hold the encoded method, an error code will be returned. - * @returns 0 on success, an error code otherwise. - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_encode_method(amqp_method_number_t methodNumber, - void *decoded, amqp_bytes_t encoded); - -/** - * Encodes a properties structure in AMQP wireformat - * - * @param [in] class_id the class id for the decoded parameter - * @param [in] decoded the properties structure (e.g., amqp_basic_properties_t) - * @param [in] encoded an allocated byte buffer for the encoded properties to - * written to. - * If the buffer isn't large enough to hold the encoded method, an - * an error code will be returned - * @returns 0 on success, an error code otherwise. - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_encode_properties(uint16_t class_id, void *decoded, - amqp_bytes_t encoded); - -/* Method field records. */ - -#define AMQP_CONNECTION_START_METHOD \ - ((amqp_method_number_t)0x000A000A) /**< connection.start method id \ - @internal 10, 10; 655370 */ -/** connection.start method fields */ -typedef struct amqp_connection_start_t_ { - uint8_t version_major; /**< version-major */ - uint8_t version_minor; /**< version-minor */ - amqp_table_t server_properties; /**< server-properties */ - amqp_bytes_t mechanisms; /**< mechanisms */ - amqp_bytes_t locales; /**< locales */ -} amqp_connection_start_t; - -#define AMQP_CONNECTION_START_OK_METHOD \ - ((amqp_method_number_t)0x000A000B) /**< connection.start-ok method id \ - @internal 10, 11; 655371 */ -/** connection.start-ok method fields */ -typedef struct amqp_connection_start_ok_t_ { - amqp_table_t client_properties; /**< client-properties */ - amqp_bytes_t mechanism; /**< mechanism */ - amqp_bytes_t response; /**< response */ - amqp_bytes_t locale; /**< locale */ -} amqp_connection_start_ok_t; - -#define AMQP_CONNECTION_SECURE_METHOD \ - ((amqp_method_number_t)0x000A0014) /**< connection.secure method id \ - @internal 10, 20; 655380 */ -/** connection.secure method fields */ -typedef struct amqp_connection_secure_t_ { - amqp_bytes_t challenge; /**< challenge */ -} amqp_connection_secure_t; - -#define AMQP_CONNECTION_SECURE_OK_METHOD \ - ((amqp_method_number_t)0x000A0015) /**< connection.secure-ok method id \ - @internal 10, 21; 655381 */ -/** connection.secure-ok method fields */ -typedef struct amqp_connection_secure_ok_t_ { - amqp_bytes_t response; /**< response */ -} amqp_connection_secure_ok_t; - -#define AMQP_CONNECTION_TUNE_METHOD \ - ((amqp_method_number_t)0x000A001E) /**< connection.tune method id \ - @internal 10, 30; 655390 */ -/** connection.tune method fields */ -typedef struct amqp_connection_tune_t_ { - uint16_t channel_max; /**< channel-max */ - uint32_t frame_max; /**< frame-max */ - uint16_t heartbeat; /**< heartbeat */ -} amqp_connection_tune_t; - -#define AMQP_CONNECTION_TUNE_OK_METHOD \ - ((amqp_method_number_t)0x000A001F) /**< connection.tune-ok method id \ - @internal 10, 31; 655391 */ -/** connection.tune-ok method fields */ -typedef struct amqp_connection_tune_ok_t_ { - uint16_t channel_max; /**< channel-max */ - uint32_t frame_max; /**< frame-max */ - uint16_t heartbeat; /**< heartbeat */ -} amqp_connection_tune_ok_t; - -#define AMQP_CONNECTION_OPEN_METHOD \ - ((amqp_method_number_t)0x000A0028) /**< connection.open method id \ - @internal 10, 40; 655400 */ -/** connection.open method fields */ -typedef struct amqp_connection_open_t_ { - amqp_bytes_t virtual_host; /**< virtual-host */ - amqp_bytes_t capabilities; /**< capabilities */ - amqp_boolean_t insist; /**< insist */ -} amqp_connection_open_t; - -#define AMQP_CONNECTION_OPEN_OK_METHOD \ - ((amqp_method_number_t)0x000A0029) /**< connection.open-ok method id \ - @internal 10, 41; 655401 */ -/** connection.open-ok method fields */ -typedef struct amqp_connection_open_ok_t_ { - amqp_bytes_t known_hosts; /**< known-hosts */ -} amqp_connection_open_ok_t; - -#define AMQP_CONNECTION_CLOSE_METHOD \ - ((amqp_method_number_t)0x000A0032) /**< connection.close method id \ - @internal 10, 50; 655410 */ -/** connection.close method fields */ -typedef struct amqp_connection_close_t_ { - uint16_t reply_code; /**< reply-code */ - amqp_bytes_t reply_text; /**< reply-text */ - uint16_t class_id; /**< class-id */ - uint16_t method_id; /**< method-id */ -} amqp_connection_close_t; - -#define AMQP_CONNECTION_CLOSE_OK_METHOD \ - ((amqp_method_number_t)0x000A0033) /**< connection.close-ok method id \ - @internal 10, 51; 655411 */ -/** connection.close-ok method fields */ -typedef struct amqp_connection_close_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_connection_close_ok_t; - -#define AMQP_CONNECTION_BLOCKED_METHOD \ - ((amqp_method_number_t)0x000A003C) /**< connection.blocked method id \ - @internal 10, 60; 655420 */ -/** connection.blocked method fields */ -typedef struct amqp_connection_blocked_t_ { - amqp_bytes_t reason; /**< reason */ -} amqp_connection_blocked_t; - -#define AMQP_CONNECTION_UNBLOCKED_METHOD \ - ((amqp_method_number_t)0x000A003D) /**< connection.unblocked method id \ - @internal 10, 61; 655421 */ -/** connection.unblocked method fields */ -typedef struct amqp_connection_unblocked_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_connection_unblocked_t; - -#define AMQP_CHANNEL_OPEN_METHOD \ - ((amqp_method_number_t)0x0014000A) /**< channel.open method id @internal \ - 20, 10; 1310730 */ -/** channel.open method fields */ -typedef struct amqp_channel_open_t_ { - amqp_bytes_t out_of_band; /**< out-of-band */ -} amqp_channel_open_t; - -#define AMQP_CHANNEL_OPEN_OK_METHOD \ - ((amqp_method_number_t)0x0014000B) /**< channel.open-ok method id \ - @internal 20, 11; 1310731 */ -/** channel.open-ok method fields */ -typedef struct amqp_channel_open_ok_t_ { - amqp_bytes_t channel_id; /**< channel-id */ -} amqp_channel_open_ok_t; - -#define AMQP_CHANNEL_FLOW_METHOD \ - ((amqp_method_number_t)0x00140014) /**< channel.flow method id @internal \ - 20, 20; 1310740 */ -/** channel.flow method fields */ -typedef struct amqp_channel_flow_t_ { - amqp_boolean_t active; /**< active */ -} amqp_channel_flow_t; - -#define AMQP_CHANNEL_FLOW_OK_METHOD \ - ((amqp_method_number_t)0x00140015) /**< channel.flow-ok method id \ - @internal 20, 21; 1310741 */ -/** channel.flow-ok method fields */ -typedef struct amqp_channel_flow_ok_t_ { - amqp_boolean_t active; /**< active */ -} amqp_channel_flow_ok_t; - -#define AMQP_CHANNEL_CLOSE_METHOD \ - ((amqp_method_number_t)0x00140028) /**< channel.close method id @internal \ - 20, 40; 1310760 */ -/** channel.close method fields */ -typedef struct amqp_channel_close_t_ { - uint16_t reply_code; /**< reply-code */ - amqp_bytes_t reply_text; /**< reply-text */ - uint16_t class_id; /**< class-id */ - uint16_t method_id; /**< method-id */ -} amqp_channel_close_t; - -#define AMQP_CHANNEL_CLOSE_OK_METHOD \ - ((amqp_method_number_t)0x00140029) /**< channel.close-ok method id \ - @internal 20, 41; 1310761 */ -/** channel.close-ok method fields */ -typedef struct amqp_channel_close_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_channel_close_ok_t; - -#define AMQP_ACCESS_REQUEST_METHOD \ - ((amqp_method_number_t)0x001E000A) /**< access.request method id @internal \ - 30, 10; 1966090 */ -/** access.request method fields */ -typedef struct amqp_access_request_t_ { - amqp_bytes_t realm; /**< realm */ - amqp_boolean_t exclusive; /**< exclusive */ - amqp_boolean_t passive; /**< passive */ - amqp_boolean_t active; /**< active */ - amqp_boolean_t write; /**< write */ - amqp_boolean_t read; /**< read */ -} amqp_access_request_t; - -#define AMQP_ACCESS_REQUEST_OK_METHOD \ - ((amqp_method_number_t)0x001E000B) /**< access.request-ok method id \ - @internal 30, 11; 1966091 */ -/** access.request-ok method fields */ -typedef struct amqp_access_request_ok_t_ { - uint16_t ticket; /**< ticket */ -} amqp_access_request_ok_t; - -#define AMQP_EXCHANGE_DECLARE_METHOD \ - ((amqp_method_number_t)0x0028000A) /**< exchange.declare method id \ - @internal 40, 10; 2621450 */ -/** exchange.declare method fields */ -typedef struct amqp_exchange_declare_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t type; /**< type */ - amqp_boolean_t passive; /**< passive */ - amqp_boolean_t durable; /**< durable */ - amqp_boolean_t auto_delete; /**< auto-delete */ - amqp_boolean_t internal; /**< internal */ - amqp_boolean_t nowait; /**< nowait */ - amqp_table_t arguments; /**< arguments */ -} amqp_exchange_declare_t; - -#define AMQP_EXCHANGE_DECLARE_OK_METHOD \ - ((amqp_method_number_t)0x0028000B) /**< exchange.declare-ok method id \ - @internal 40, 11; 2621451 */ -/** exchange.declare-ok method fields */ -typedef struct amqp_exchange_declare_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_exchange_declare_ok_t; - -#define AMQP_EXCHANGE_DELETE_METHOD \ - ((amqp_method_number_t)0x00280014) /**< exchange.delete method id \ - @internal 40, 20; 2621460 */ -/** exchange.delete method fields */ -typedef struct amqp_exchange_delete_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t exchange; /**< exchange */ - amqp_boolean_t if_unused; /**< if-unused */ - amqp_boolean_t nowait; /**< nowait */ -} amqp_exchange_delete_t; - -#define AMQP_EXCHANGE_DELETE_OK_METHOD \ - ((amqp_method_number_t)0x00280015) /**< exchange.delete-ok method id \ - @internal 40, 21; 2621461 */ -/** exchange.delete-ok method fields */ -typedef struct amqp_exchange_delete_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_exchange_delete_ok_t; - -#define AMQP_EXCHANGE_BIND_METHOD \ - ((amqp_method_number_t)0x0028001E) /**< exchange.bind method id @internal \ - 40, 30; 2621470 */ -/** exchange.bind method fields */ -typedef struct amqp_exchange_bind_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t destination; /**< destination */ - amqp_bytes_t source; /**< source */ - amqp_bytes_t routing_key; /**< routing-key */ - amqp_boolean_t nowait; /**< nowait */ - amqp_table_t arguments; /**< arguments */ -} amqp_exchange_bind_t; - -#define AMQP_EXCHANGE_BIND_OK_METHOD \ - ((amqp_method_number_t)0x0028001F) /**< exchange.bind-ok method id \ - @internal 40, 31; 2621471 */ -/** exchange.bind-ok method fields */ -typedef struct amqp_exchange_bind_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_exchange_bind_ok_t; - -#define AMQP_EXCHANGE_UNBIND_METHOD \ - ((amqp_method_number_t)0x00280028) /**< exchange.unbind method id \ - @internal 40, 40; 2621480 */ -/** exchange.unbind method fields */ -typedef struct amqp_exchange_unbind_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t destination; /**< destination */ - amqp_bytes_t source; /**< source */ - amqp_bytes_t routing_key; /**< routing-key */ - amqp_boolean_t nowait; /**< nowait */ - amqp_table_t arguments; /**< arguments */ -} amqp_exchange_unbind_t; - -#define AMQP_EXCHANGE_UNBIND_OK_METHOD \ - ((amqp_method_number_t)0x00280033) /**< exchange.unbind-ok method id \ - @internal 40, 51; 2621491 */ -/** exchange.unbind-ok method fields */ -typedef struct amqp_exchange_unbind_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_exchange_unbind_ok_t; - -#define AMQP_QUEUE_DECLARE_METHOD \ - ((amqp_method_number_t)0x0032000A) /**< queue.declare method id @internal \ - 50, 10; 3276810 */ -/** queue.declare method fields */ -typedef struct amqp_queue_declare_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_boolean_t passive; /**< passive */ - amqp_boolean_t durable; /**< durable */ - amqp_boolean_t exclusive; /**< exclusive */ - amqp_boolean_t auto_delete; /**< auto-delete */ - amqp_boolean_t nowait; /**< nowait */ - amqp_table_t arguments; /**< arguments */ -} amqp_queue_declare_t; - -#define AMQP_QUEUE_DECLARE_OK_METHOD \ - ((amqp_method_number_t)0x0032000B) /**< queue.declare-ok method id \ - @internal 50, 11; 3276811 */ -/** queue.declare-ok method fields */ -typedef struct amqp_queue_declare_ok_t_ { - amqp_bytes_t queue; /**< queue */ - uint32_t message_count; /**< message-count */ - uint32_t consumer_count; /**< consumer-count */ -} amqp_queue_declare_ok_t; - -#define AMQP_QUEUE_BIND_METHOD \ - ((amqp_method_number_t)0x00320014) /**< queue.bind method id @internal 50, \ - 20; 3276820 */ -/** queue.bind method fields */ -typedef struct amqp_queue_bind_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t routing_key; /**< routing-key */ - amqp_boolean_t nowait; /**< nowait */ - amqp_table_t arguments; /**< arguments */ -} amqp_queue_bind_t; - -#define AMQP_QUEUE_BIND_OK_METHOD \ - ((amqp_method_number_t)0x00320015) /**< queue.bind-ok method id @internal \ - 50, 21; 3276821 */ -/** queue.bind-ok method fields */ -typedef struct amqp_queue_bind_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_queue_bind_ok_t; - -#define AMQP_QUEUE_PURGE_METHOD \ - ((amqp_method_number_t)0x0032001E) /**< queue.purge method id @internal \ - 50, 30; 3276830 */ -/** queue.purge method fields */ -typedef struct amqp_queue_purge_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_boolean_t nowait; /**< nowait */ -} amqp_queue_purge_t; - -#define AMQP_QUEUE_PURGE_OK_METHOD \ - ((amqp_method_number_t)0x0032001F) /**< queue.purge-ok method id @internal \ - 50, 31; 3276831 */ -/** queue.purge-ok method fields */ -typedef struct amqp_queue_purge_ok_t_ { - uint32_t message_count; /**< message-count */ -} amqp_queue_purge_ok_t; - -#define AMQP_QUEUE_DELETE_METHOD \ - ((amqp_method_number_t)0x00320028) /**< queue.delete method id @internal \ - 50, 40; 3276840 */ -/** queue.delete method fields */ -typedef struct amqp_queue_delete_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_boolean_t if_unused; /**< if-unused */ - amqp_boolean_t if_empty; /**< if-empty */ - amqp_boolean_t nowait; /**< nowait */ -} amqp_queue_delete_t; - -#define AMQP_QUEUE_DELETE_OK_METHOD \ - ((amqp_method_number_t)0x00320029) /**< queue.delete-ok method id \ - @internal 50, 41; 3276841 */ -/** queue.delete-ok method fields */ -typedef struct amqp_queue_delete_ok_t_ { - uint32_t message_count; /**< message-count */ -} amqp_queue_delete_ok_t; - -#define AMQP_QUEUE_UNBIND_METHOD \ - ((amqp_method_number_t)0x00320032) /**< queue.unbind method id @internal \ - 50, 50; 3276850 */ -/** queue.unbind method fields */ -typedef struct amqp_queue_unbind_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t routing_key; /**< routing-key */ - amqp_table_t arguments; /**< arguments */ -} amqp_queue_unbind_t; - -#define AMQP_QUEUE_UNBIND_OK_METHOD \ - ((amqp_method_number_t)0x00320033) /**< queue.unbind-ok method id \ - @internal 50, 51; 3276851 */ -/** queue.unbind-ok method fields */ -typedef struct amqp_queue_unbind_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_queue_unbind_ok_t; - -#define AMQP_BASIC_QOS_METHOD \ - ((amqp_method_number_t)0x003C000A) /**< basic.qos method id @internal 60, \ - 10; 3932170 */ -/** basic.qos method fields */ -typedef struct amqp_basic_qos_t_ { - uint32_t prefetch_size; /**< prefetch-size */ - uint16_t prefetch_count; /**< prefetch-count */ - amqp_boolean_t global; /**< global */ -} amqp_basic_qos_t; - -#define AMQP_BASIC_QOS_OK_METHOD \ - ((amqp_method_number_t)0x003C000B) /**< basic.qos-ok method id @internal \ - 60, 11; 3932171 */ -/** basic.qos-ok method fields */ -typedef struct amqp_basic_qos_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_basic_qos_ok_t; - -#define AMQP_BASIC_CONSUME_METHOD \ - ((amqp_method_number_t)0x003C0014) /**< basic.consume method id @internal \ - 60, 20; 3932180 */ -/** basic.consume method fields */ -typedef struct amqp_basic_consume_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_bytes_t consumer_tag; /**< consumer-tag */ - amqp_boolean_t no_local; /**< no-local */ - amqp_boolean_t no_ack; /**< no-ack */ - amqp_boolean_t exclusive; /**< exclusive */ - amqp_boolean_t nowait; /**< nowait */ - amqp_table_t arguments; /**< arguments */ -} amqp_basic_consume_t; - -#define AMQP_BASIC_CONSUME_OK_METHOD \ - ((amqp_method_number_t)0x003C0015) /**< basic.consume-ok method id \ - @internal 60, 21; 3932181 */ -/** basic.consume-ok method fields */ -typedef struct amqp_basic_consume_ok_t_ { - amqp_bytes_t consumer_tag; /**< consumer-tag */ -} amqp_basic_consume_ok_t; - -#define AMQP_BASIC_CANCEL_METHOD \ - ((amqp_method_number_t)0x003C001E) /**< basic.cancel method id @internal \ - 60, 30; 3932190 */ -/** basic.cancel method fields */ -typedef struct amqp_basic_cancel_t_ { - amqp_bytes_t consumer_tag; /**< consumer-tag */ - amqp_boolean_t nowait; /**< nowait */ -} amqp_basic_cancel_t; - -#define AMQP_BASIC_CANCEL_OK_METHOD \ - ((amqp_method_number_t)0x003C001F) /**< basic.cancel-ok method id \ - @internal 60, 31; 3932191 */ -/** basic.cancel-ok method fields */ -typedef struct amqp_basic_cancel_ok_t_ { - amqp_bytes_t consumer_tag; /**< consumer-tag */ -} amqp_basic_cancel_ok_t; - -#define AMQP_BASIC_PUBLISH_METHOD \ - ((amqp_method_number_t)0x003C0028) /**< basic.publish method id @internal \ - 60, 40; 3932200 */ -/** basic.publish method fields */ -typedef struct amqp_basic_publish_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t routing_key; /**< routing-key */ - amqp_boolean_t mandatory; /**< mandatory */ - amqp_boolean_t immediate; /**< immediate */ -} amqp_basic_publish_t; - -#define AMQP_BASIC_RETURN_METHOD \ - ((amqp_method_number_t)0x003C0032) /**< basic.return method id @internal \ - 60, 50; 3932210 */ -/** basic.return method fields */ -typedef struct amqp_basic_return_t_ { - uint16_t reply_code; /**< reply-code */ - amqp_bytes_t reply_text; /**< reply-text */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t routing_key; /**< routing-key */ -} amqp_basic_return_t; - -#define AMQP_BASIC_DELIVER_METHOD \ - ((amqp_method_number_t)0x003C003C) /**< basic.deliver method id @internal \ - 60, 60; 3932220 */ -/** basic.deliver method fields */ -typedef struct amqp_basic_deliver_t_ { - amqp_bytes_t consumer_tag; /**< consumer-tag */ - uint64_t delivery_tag; /**< delivery-tag */ - amqp_boolean_t redelivered; /**< redelivered */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t routing_key; /**< routing-key */ -} amqp_basic_deliver_t; - -#define AMQP_BASIC_GET_METHOD \ - ((amqp_method_number_t)0x003C0046) /**< basic.get method id @internal 60, \ - 70; 3932230 */ -/** basic.get method fields */ -typedef struct amqp_basic_get_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_boolean_t no_ack; /**< no-ack */ -} amqp_basic_get_t; - -#define AMQP_BASIC_GET_OK_METHOD \ - ((amqp_method_number_t)0x003C0047) /**< basic.get-ok method id @internal \ - 60, 71; 3932231 */ -/** basic.get-ok method fields */ -typedef struct amqp_basic_get_ok_t_ { - uint64_t delivery_tag; /**< delivery-tag */ - amqp_boolean_t redelivered; /**< redelivered */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t routing_key; /**< routing-key */ - uint32_t message_count; /**< message-count */ -} amqp_basic_get_ok_t; - -#define AMQP_BASIC_GET_EMPTY_METHOD \ - ((amqp_method_number_t)0x003C0048) /**< basic.get-empty method id \ - @internal 60, 72; 3932232 */ -/** basic.get-empty method fields */ -typedef struct amqp_basic_get_empty_t_ { - amqp_bytes_t cluster_id; /**< cluster-id */ -} amqp_basic_get_empty_t; - -#define AMQP_BASIC_ACK_METHOD \ - ((amqp_method_number_t)0x003C0050) /**< basic.ack method id @internal 60, \ - 80; 3932240 */ -/** basic.ack method fields */ -typedef struct amqp_basic_ack_t_ { - uint64_t delivery_tag; /**< delivery-tag */ - amqp_boolean_t multiple; /**< multiple */ -} amqp_basic_ack_t; - -#define AMQP_BASIC_REJECT_METHOD \ - ((amqp_method_number_t)0x003C005A) /**< basic.reject method id @internal \ - 60, 90; 3932250 */ -/** basic.reject method fields */ -typedef struct amqp_basic_reject_t_ { - uint64_t delivery_tag; /**< delivery-tag */ - amqp_boolean_t requeue; /**< requeue */ -} amqp_basic_reject_t; - -#define AMQP_BASIC_RECOVER_ASYNC_METHOD \ - ((amqp_method_number_t)0x003C0064) /**< basic.recover-async method id \ - @internal 60, 100; 3932260 */ -/** basic.recover-async method fields */ -typedef struct amqp_basic_recover_async_t_ { - amqp_boolean_t requeue; /**< requeue */ -} amqp_basic_recover_async_t; - -#define AMQP_BASIC_RECOVER_METHOD \ - ((amqp_method_number_t)0x003C006E) /**< basic.recover method id @internal \ - 60, 110; 3932270 */ -/** basic.recover method fields */ -typedef struct amqp_basic_recover_t_ { - amqp_boolean_t requeue; /**< requeue */ -} amqp_basic_recover_t; - -#define AMQP_BASIC_RECOVER_OK_METHOD \ - ((amqp_method_number_t)0x003C006F) /**< basic.recover-ok method id \ - @internal 60, 111; 3932271 */ -/** basic.recover-ok method fields */ -typedef struct amqp_basic_recover_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_basic_recover_ok_t; - -#define AMQP_BASIC_NACK_METHOD \ - ((amqp_method_number_t)0x003C0078) /**< basic.nack method id @internal 60, \ - 120; 3932280 */ -/** basic.nack method fields */ -typedef struct amqp_basic_nack_t_ { - uint64_t delivery_tag; /**< delivery-tag */ - amqp_boolean_t multiple; /**< multiple */ - amqp_boolean_t requeue; /**< requeue */ -} amqp_basic_nack_t; - -#define AMQP_TX_SELECT_METHOD \ - ((amqp_method_number_t)0x005A000A) /**< tx.select method id @internal 90, \ - 10; 5898250 */ -/** tx.select method fields */ -typedef struct amqp_tx_select_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_select_t; - -#define AMQP_TX_SELECT_OK_METHOD \ - ((amqp_method_number_t)0x005A000B) /**< tx.select-ok method id @internal \ - 90, 11; 5898251 */ -/** tx.select-ok method fields */ -typedef struct amqp_tx_select_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_select_ok_t; - -#define AMQP_TX_COMMIT_METHOD \ - ((amqp_method_number_t)0x005A0014) /**< tx.commit method id @internal 90, \ - 20; 5898260 */ -/** tx.commit method fields */ -typedef struct amqp_tx_commit_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_commit_t; - -#define AMQP_TX_COMMIT_OK_METHOD \ - ((amqp_method_number_t)0x005A0015) /**< tx.commit-ok method id @internal \ - 90, 21; 5898261 */ -/** tx.commit-ok method fields */ -typedef struct amqp_tx_commit_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_commit_ok_t; - -#define AMQP_TX_ROLLBACK_METHOD \ - ((amqp_method_number_t)0x005A001E) /**< tx.rollback method id @internal \ - 90, 30; 5898270 */ -/** tx.rollback method fields */ -typedef struct amqp_tx_rollback_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_rollback_t; - -#define AMQP_TX_ROLLBACK_OK_METHOD \ - ((amqp_method_number_t)0x005A001F) /**< tx.rollback-ok method id @internal \ - 90, 31; 5898271 */ -/** tx.rollback-ok method fields */ -typedef struct amqp_tx_rollback_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_rollback_ok_t; - -#define AMQP_CONFIRM_SELECT_METHOD \ - ((amqp_method_number_t)0x0055000A) /**< confirm.select method id @internal \ - 85, 10; 5570570 */ -/** confirm.select method fields */ -typedef struct amqp_confirm_select_t_ { - amqp_boolean_t nowait; /**< nowait */ -} amqp_confirm_select_t; - -#define AMQP_CONFIRM_SELECT_OK_METHOD \ - ((amqp_method_number_t)0x0055000B) /**< confirm.select-ok method id \ - @internal 85, 11; 5570571 */ -/** confirm.select-ok method fields */ -typedef struct amqp_confirm_select_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_confirm_select_ok_t; - -/* Class property records. */ -#define AMQP_CONNECTION_CLASS \ - (0x000A) /**< connection class id @internal 10 \ - */ -/** connection class properties */ -typedef struct amqp_connection_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_connection_properties_t; - -#define AMQP_CHANNEL_CLASS (0x0014) /**< channel class id @internal 20 */ -/** channel class properties */ -typedef struct amqp_channel_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_channel_properties_t; - -#define AMQP_ACCESS_CLASS (0x001E) /**< access class id @internal 30 */ -/** access class properties */ -typedef struct amqp_access_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_access_properties_t; - -#define AMQP_EXCHANGE_CLASS (0x0028) /**< exchange class id @internal 40 */ -/** exchange class properties */ -typedef struct amqp_exchange_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_exchange_properties_t; - -#define AMQP_QUEUE_CLASS (0x0032) /**< queue class id @internal 50 */ -/** queue class properties */ -typedef struct amqp_queue_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_queue_properties_t; - -#define AMQP_BASIC_CLASS (0x003C) /**< basic class id @internal 60 */ -#define AMQP_BASIC_CONTENT_TYPE_FLAG (1 << 15) -#define AMQP_BASIC_CONTENT_ENCODING_FLAG (1 << 14) -#define AMQP_BASIC_HEADERS_FLAG (1 << 13) -#define AMQP_BASIC_DELIVERY_MODE_FLAG (1 << 12) -#define AMQP_BASIC_PRIORITY_FLAG (1 << 11) -#define AMQP_BASIC_CORRELATION_ID_FLAG (1 << 10) -#define AMQP_BASIC_REPLY_TO_FLAG (1 << 9) -#define AMQP_BASIC_EXPIRATION_FLAG (1 << 8) -#define AMQP_BASIC_MESSAGE_ID_FLAG (1 << 7) -#define AMQP_BASIC_TIMESTAMP_FLAG (1 << 6) -#define AMQP_BASIC_TYPE_FLAG (1 << 5) -#define AMQP_BASIC_USER_ID_FLAG (1 << 4) -#define AMQP_BASIC_APP_ID_FLAG (1 << 3) -#define AMQP_BASIC_CLUSTER_ID_FLAG (1 << 2) -/** basic class properties */ -typedef struct amqp_basic_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - amqp_bytes_t content_type; /**< content-type */ - amqp_bytes_t content_encoding; /**< content-encoding */ - amqp_table_t headers; /**< headers */ - uint8_t delivery_mode; /**< delivery-mode */ - uint8_t priority; /**< priority */ - amqp_bytes_t correlation_id; /**< correlation-id */ - amqp_bytes_t reply_to; /**< reply-to */ - amqp_bytes_t expiration; /**< expiration */ - amqp_bytes_t message_id; /**< message-id */ - uint64_t timestamp; /**< timestamp */ - amqp_bytes_t type; /**< type */ - amqp_bytes_t user_id; /**< user-id */ - amqp_bytes_t app_id; /**< app-id */ - amqp_bytes_t cluster_id; /**< cluster-id */ -} amqp_basic_properties_t; - -#define AMQP_TX_CLASS (0x005A) /**< tx class id @internal 90 */ -/** tx class properties */ -typedef struct amqp_tx_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_properties_t; - -#define AMQP_CONFIRM_CLASS (0x0055) /**< confirm class id @internal 85 */ -/** confirm class properties */ -typedef struct amqp_confirm_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_confirm_properties_t; - -/* API functions for methods */ - -/** - * amqp_channel_open - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @returns amqp_channel_open_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_channel_open_ok_t *AMQP_CALL - amqp_channel_open(amqp_connection_state_t state, amqp_channel_t channel); -/** - * amqp_channel_flow - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] active active - * @returns amqp_channel_flow_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_channel_flow_ok_t *AMQP_CALL - amqp_channel_flow(amqp_connection_state_t state, amqp_channel_t channel, - amqp_boolean_t active); -/** - * amqp_exchange_declare - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] exchange exchange - * @param [in] type type - * @param [in] passive passive - * @param [in] durable durable - * @param [in] auto_delete auto_delete - * @param [in] internal internal - * @param [in] arguments arguments - * @returns amqp_exchange_declare_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_exchange_declare_ok_t *AMQP_CALL amqp_exchange_declare( - amqp_connection_state_t state, amqp_channel_t channel, - amqp_bytes_t exchange, amqp_bytes_t type, amqp_boolean_t passive, - amqp_boolean_t durable, amqp_boolean_t auto_delete, amqp_boolean_t internal, - amqp_table_t arguments); -/** - * amqp_exchange_delete - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] exchange exchange - * @param [in] if_unused if_unused - * @returns amqp_exchange_delete_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_exchange_delete_ok_t *AMQP_CALL - amqp_exchange_delete(amqp_connection_state_t state, amqp_channel_t channel, - amqp_bytes_t exchange, amqp_boolean_t if_unused); -/** - * amqp_exchange_bind - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] destination destination - * @param [in] source source - * @param [in] routing_key routing_key - * @param [in] arguments arguments - * @returns amqp_exchange_bind_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_exchange_bind_ok_t *AMQP_CALL - amqp_exchange_bind(amqp_connection_state_t state, amqp_channel_t channel, - amqp_bytes_t destination, amqp_bytes_t source, - amqp_bytes_t routing_key, amqp_table_t arguments); -/** - * amqp_exchange_unbind - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] destination destination - * @param [in] source source - * @param [in] routing_key routing_key - * @param [in] arguments arguments - * @returns amqp_exchange_unbind_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_exchange_unbind_ok_t *AMQP_CALL - amqp_exchange_unbind(amqp_connection_state_t state, amqp_channel_t channel, - amqp_bytes_t destination, amqp_bytes_t source, - amqp_bytes_t routing_key, amqp_table_t arguments); -/** - * amqp_queue_declare - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] queue queue - * @param [in] passive passive - * @param [in] durable durable - * @param [in] exclusive exclusive - * @param [in] auto_delete auto_delete - * @param [in] arguments arguments - * @returns amqp_queue_declare_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_queue_declare_ok_t *AMQP_CALL amqp_queue_declare( - amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, - amqp_boolean_t passive, amqp_boolean_t durable, amqp_boolean_t exclusive, - amqp_boolean_t auto_delete, amqp_table_t arguments); -/** - * amqp_queue_bind - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] queue queue - * @param [in] exchange exchange - * @param [in] routing_key routing_key - * @param [in] arguments arguments - * @returns amqp_queue_bind_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_queue_bind_ok_t *AMQP_CALL amqp_queue_bind( - amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, - amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_table_t arguments); -/** - * amqp_queue_purge - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] queue queue - * @returns amqp_queue_purge_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_queue_purge_ok_t *AMQP_CALL amqp_queue_purge(amqp_connection_state_t state, - amqp_channel_t channel, - amqp_bytes_t queue); -/** - * amqp_queue_delete - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] queue queue - * @param [in] if_unused if_unused - * @param [in] if_empty if_empty - * @returns amqp_queue_delete_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_queue_delete_ok_t *AMQP_CALL amqp_queue_delete( - amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, - amqp_boolean_t if_unused, amqp_boolean_t if_empty); -/** - * amqp_queue_unbind - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] queue queue - * @param [in] exchange exchange - * @param [in] routing_key routing_key - * @param [in] arguments arguments - * @returns amqp_queue_unbind_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_queue_unbind_ok_t *AMQP_CALL amqp_queue_unbind( - amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, - amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_table_t arguments); -/** - * amqp_basic_qos - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] prefetch_size prefetch_size - * @param [in] prefetch_count prefetch_count - * @param [in] global global - * @returns amqp_basic_qos_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_basic_qos_ok_t *AMQP_CALL amqp_basic_qos(amqp_connection_state_t state, - amqp_channel_t channel, - uint32_t prefetch_size, - uint16_t prefetch_count, - amqp_boolean_t global); -/** - * amqp_basic_consume - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] queue queue - * @param [in] consumer_tag consumer_tag - * @param [in] no_local no_local - * @param [in] no_ack no_ack - * @param [in] exclusive exclusive - * @param [in] arguments arguments - * @returns amqp_basic_consume_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_basic_consume_ok_t *AMQP_CALL amqp_basic_consume( - amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, - amqp_bytes_t consumer_tag, amqp_boolean_t no_local, amqp_boolean_t no_ack, - amqp_boolean_t exclusive, amqp_table_t arguments); -/** - * amqp_basic_cancel - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] consumer_tag consumer_tag - * @returns amqp_basic_cancel_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_basic_cancel_ok_t *AMQP_CALL - amqp_basic_cancel(amqp_connection_state_t state, amqp_channel_t channel, - amqp_bytes_t consumer_tag); -/** - * amqp_basic_recover - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] requeue requeue - * @returns amqp_basic_recover_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_basic_recover_ok_t *AMQP_CALL - amqp_basic_recover(amqp_connection_state_t state, amqp_channel_t channel, - amqp_boolean_t requeue); -/** - * amqp_tx_select - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @returns amqp_tx_select_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_tx_select_ok_t *AMQP_CALL amqp_tx_select(amqp_connection_state_t state, - amqp_channel_t channel); -/** - * amqp_tx_commit - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @returns amqp_tx_commit_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_tx_commit_ok_t *AMQP_CALL amqp_tx_commit(amqp_connection_state_t state, - amqp_channel_t channel); -/** - * amqp_tx_rollback - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @returns amqp_tx_rollback_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_tx_rollback_ok_t *AMQP_CALL amqp_tx_rollback(amqp_connection_state_t state, - amqp_channel_t channel); -/** - * amqp_confirm_select - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @returns amqp_confirm_select_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_confirm_select_ok_t *AMQP_CALL - amqp_confirm_select(amqp_connection_state_t state, amqp_channel_t channel); - -AMQP_END_DECLS - -#endif /* AMQP_FRAMING_H */ diff --git a/ext/librabbitmq/centos_x64/include/amqp_tcp_socket.h b/ext/librabbitmq/centos_x64/include/amqp_tcp_socket.h deleted file mode 100644 index 3e9d82f54..000000000 --- a/ext/librabbitmq/centos_x64/include/amqp_tcp_socket.h +++ /dev/null @@ -1,68 +0,0 @@ -/** \file */ -/* - * Portions created by Alan Antonuk are Copyright (c) 2013-2014 Alan Antonuk. - * All Rights Reserved. - * - * Portions created by Michael Steinert are Copyright (c) 2012-2013 Michael - * Steinert. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -/** - * A TCP socket connection. - */ - -#ifndef AMQP_TCP_SOCKET_H -#define AMQP_TCP_SOCKET_H - -#include - -AMQP_BEGIN_DECLS - -/** - * Create a new TCP socket. - * - * Call amqp_connection_close() to release socket resources. - * - * \return A new socket object or NULL if an error occurred. - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_socket_t *AMQP_CALL amqp_tcp_socket_new(amqp_connection_state_t state); - -/** - * Assign an open file descriptor to a socket object. - * - * This function must not be used in conjunction with amqp_socket_open(), i.e. - * the socket connection should already be open(2) when this function is - * called. - * - * \param [in,out] self A TCP socket object. - * \param [in] sockfd An open socket descriptor. - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_tcp_socket_set_sockfd(amqp_socket_t *self, int sockfd); - -AMQP_END_DECLS - -#endif /* AMQP_TCP_SOCKET_H */ diff --git a/ext/librabbitmq/centos_x64/lib/librabbitmq.a b/ext/librabbitmq/centos_x64/lib/librabbitmq.a deleted file mode 100644 index d5c3e8b42777f42e6ce8ffde1f6810f43361c8aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 139468 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 diff --git a/ext/librabbitmq/macos/include/amqp.h b/ext/librabbitmq/macos/include/amqp.h deleted file mode 100644 index 2983b1665..000000000 --- a/ext/librabbitmq/macos/include/amqp.h +++ /dev/null @@ -1,2538 +0,0 @@ -/** \file */ -/* - * ***** BEGIN LICENSE BLOCK ***** - * Version: MIT - * - * Portions created by Alan Antonuk are Copyright (c) 2012-2014 - * Alan Antonuk. All Rights Reserved. - * - * Portions created by VMware are Copyright (c) 2007-2012 VMware, Inc. - * All Rights Reserved. - * - * Portions created by Tony Garnock-Jones are Copyright (c) 2009-2010 - * VMware, Inc. and Tony Garnock-Jones. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * ***** END LICENSE BLOCK ***** - */ - -#ifndef AMQP_H -#define AMQP_H - -/** \cond HIDE_FROM_DOXYGEN */ - -#ifdef __cplusplus -#define AMQP_BEGIN_DECLS extern "C" { -#define AMQP_END_DECLS } -#else -#define AMQP_BEGIN_DECLS -#define AMQP_END_DECLS -#endif - -/* - * \internal - * Important API decorators: - * AMQP_PUBLIC_FUNCTION - a public API function - * AMQP_PUBLIC_VARIABLE - a public API external variable - * AMQP_CALL - calling convension (used on Win32) - */ - -#if defined(_WIN32) && defined(_MSC_VER) -#if defined(AMQP_BUILD) && !defined(AMQP_STATIC) -#define AMQP_PUBLIC_FUNCTION __declspec(dllexport) -#define AMQP_PUBLIC_VARIABLE __declspec(dllexport) extern -#else -#define AMQP_PUBLIC_FUNCTION -#if !defined(AMQP_STATIC) -#define AMQP_PUBLIC_VARIABLE __declspec(dllimport) extern -#else -#define AMQP_PUBLIC_VARIABLE extern -#endif -#endif -#define AMQP_CALL __cdecl - -#elif defined(_WIN32) && defined(__BORLANDC__) -#if defined(AMQP_BUILD) && !defined(AMQP_STATIC) -#define AMQP_PUBLIC_FUNCTION __declspec(dllexport) -#define AMQP_PUBLIC_VARIABLE __declspec(dllexport) extern -#else -#define AMQP_PUBLIC_FUNCTION -#if !defined(AMQP_STATIC) -#define AMQP_PUBLIC_VARIABLE __declspec(dllimport) extern -#else -#define AMQP_PUBLIC_VARIABLE extern -#endif -#endif -#define AMQP_CALL __cdecl - -#elif defined(_WIN32) && defined(__MINGW32__) -#if defined(AMQP_BUILD) && !defined(AMQP_STATIC) -#define AMQP_PUBLIC_FUNCTION __declspec(dllexport) -#define AMQP_PUBLIC_VARIABLE __declspec(dllexport) extern -#else -#define AMQP_PUBLIC_FUNCTION -#if !defined(AMQP_STATIC) -#define AMQP_PUBLIC_VARIABLE __declspec(dllimport) extern -#else -#define AMQP_PUBLIC_VARIABLE extern -#endif -#endif -#define AMQP_CALL __cdecl - -#elif defined(_WIN32) && defined(__CYGWIN__) -#if defined(AMQP_BUILD) && !defined(AMQP_STATIC) -#define AMQP_PUBLIC_FUNCTION __declspec(dllexport) -#define AMQP_PUBLIC_VARIABLE __declspec(dllexport) -#else -#define AMQP_PUBLIC_FUNCTION -#if !defined(AMQP_STATIC) -#define AMQP_PUBLIC_VARIABLE __declspec(dllimport) extern -#else -#define AMQP_PUBLIC_VARIABLE extern -#endif -#endif -#define AMQP_CALL __cdecl - -#elif defined(__GNUC__) && __GNUC__ >= 4 -#define AMQP_PUBLIC_FUNCTION __attribute__((visibility("default"))) -#define AMQP_PUBLIC_VARIABLE __attribute__((visibility("default"))) extern -#define AMQP_CALL -#else -#define AMQP_PUBLIC_FUNCTION -#define AMQP_PUBLIC_VARIABLE extern -#define AMQP_CALL -#endif - -#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) -#define AMQP_DEPRECATED(function) function __attribute__((__deprecated__)) -#elif defined(_MSC_VER) -#define AMQP_DEPRECATED(function) __declspec(deprecated) function -#else -#define AMQP_DEPRECATED(function) -#endif - -/* Define ssize_t on Win32/64 platforms - See: http://lists.cs.uiuc.edu/pipermail/llvmdev/2010-April/030649.html for - details - */ -#if !defined(_W64) -#if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 -#define _W64 __w64 -#else -#define _W64 -#endif -#endif - -#ifdef _MSC_VER -#ifdef _WIN64 -typedef __int64 ssize_t; -#else -typedef _W64 int ssize_t; -#endif -#endif - -#if defined(_WIN32) && defined(__MINGW32__) -#include -#endif - -/** \endcond */ - -#include -#include - -struct timeval; - -AMQP_BEGIN_DECLS - -/** - * \def AMQP_VERSION_MAJOR - * - * Major library version number compile-time constant - * - * The major version is incremented when backwards incompatible API changes - * are made. - * - * \sa AMQP_VERSION, AMQP_VERSION_STRING - * - * \since v0.4.0 - */ - -/** - * \def AMQP_VERSION_MINOR - * - * Minor library version number compile-time constant - * - * The minor version is incremented when new APIs are added. Existing APIs - * are left alone. - * - * \sa AMQP_VERSION, AMQP_VERSION_STRING - * - * \since v0.4.0 - */ - -/** - * \def AMQP_VERSION_PATCH - * - * Patch library version number compile-time constant - * - * The patch version is incremented when library code changes, but the API - * is not changed. - * - * \sa AMQP_VERSION, AMQP_VERSION_STRING - * - * \since v0.4.0 - */ - -/** - * \def AMQP_VERSION_IS_RELEASE - * - * Version constant set to 1 for tagged release, 0 otherwise - * - * NOTE: versions that are not tagged releases are not guaranteed to be API/ABI - * compatible with older releases, and may change commit-to-commit. - * - * \sa AMQP_VERSION, AMQP_VERSION_STRING - * - * \since v0.4.0 - */ -/* - * Developer note: when changing these, be sure to update SOVERSION constants - * in CMakeLists.txt and configure.ac - */ - -#define AMQP_VERSION_MAJOR 0 -#define AMQP_VERSION_MINOR 10 -#define AMQP_VERSION_PATCH 0 -#define AMQP_VERSION_IS_RELEASE 0 - -/** - * \def AMQP_VERSION_CODE - * - * Helper macro to geneate a packed version code suitable for - * comparison with AMQP_VERSION. - * - * \sa amqp_version_number() AMQP_VERSION_MAJOR, AMQP_VERSION_MINOR, - * AMQP_VERSION_PATCH, AMQP_VERSION_IS_RELEASE, AMQP_VERSION - * - * \since v0.6.1 - */ -#define AMQP_VERSION_CODE(major, minor, patch, release) \ - ((major << 24) | (minor << 16) | (patch << 8) | (release)) - -/** - * \def AMQP_VERSION - * - * Packed version number - * - * AMQP_VERSION is a 4-byte unsigned integer with the most significant byte - * set to AMQP_VERSION_MAJOR, the second most significant byte set to - * AMQP_VERSION_MINOR, third most significant byte set to AMQP_VERSION_PATCH, - * and the lowest byte set to AMQP_VERSION_IS_RELEASE. - * - * For example version 2.3.4 which is released version would be encoded as - * 0x02030401 - * - * \sa amqp_version_number() AMQP_VERSION_MAJOR, AMQP_VERSION_MINOR, - * AMQP_VERSION_PATCH, AMQP_VERSION_IS_RELEASE, AMQP_VERSION_CODE - * - * \since v0.4.0 - */ -#define AMQP_VERSION \ - AMQP_VERSION_CODE(AMQP_VERSION_MAJOR, AMQP_VERSION_MINOR, \ - AMQP_VERSION_PATCH, AMQP_VERSION_IS_RELEASE) - -/** \cond HIDE_FROM_DOXYGEN */ -#define AMQ_STRINGIFY(s) AMQ_STRINGIFY_HELPER(s) -#define AMQ_STRINGIFY_HELPER(s) #s - -#define AMQ_VERSION_STRING \ - AMQ_STRINGIFY(AMQP_VERSION_MAJOR) \ - "." AMQ_STRINGIFY(AMQP_VERSION_MINOR) "." AMQ_STRINGIFY(AMQP_VERSION_PATCH) -/** \endcond */ - -/** - * \def AMQP_VERSION_STRING - * - * Version string compile-time constant - * - * Non-released versions of the library will have "-pre" appended to the - * version string - * - * \sa amqp_version() - * - * \since v0.4.0 - */ -#if AMQP_VERSION_IS_RELEASE -#define AMQP_VERSION_STRING AMQ_VERSION_STRING -#else -#define AMQP_VERSION_STRING AMQ_VERSION_STRING "-pre" -#endif - -/** - * Returns the rabbitmq-c version as a packed integer. - * - * See \ref AMQP_VERSION - * - * \return packed 32-bit integer representing version of library at runtime - * - * \sa AMQP_VERSION, amqp_version() - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -uint32_t AMQP_CALL amqp_version_number(void); - -/** - * Returns the rabbitmq-c version as a string. - * - * See \ref AMQP_VERSION_STRING - * - * \return a statically allocated string describing the version of rabbitmq-c. - * - * \sa amqp_version_number(), AMQP_VERSION_STRING, AMQP_VERSION - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -char const *AMQP_CALL amqp_version(void); - -/** - * \def AMQP_DEFAULT_FRAME_SIZE - * - * Default frame size (128Kb) - * - * \sa amqp_login(), amqp_login_with_properties() - * - * \since v0.4.0 - */ -#define AMQP_DEFAULT_FRAME_SIZE 131072 - -/** - * \def AMQP_DEFAULT_MAX_CHANNELS - * - * Default maximum number of channels (2047, RabbitMQ default limit of 2048, - * minus 1 for channel 0). RabbitMQ set a default limit of 2048 channels per - * connection in v3.7.5 to prevent broken clients from leaking too many - * channels. - * - * \sa amqp_login(), amqp_login_with_properties() - * - * \since v0.4.0 - */ -#define AMQP_DEFAULT_MAX_CHANNELS 2047 - -/** - * \def AMQP_DEFAULT_HEARTBEAT - * - * Default heartbeat interval (0, heartbeat disabled) - * - * \sa amqp_login(), amqp_login_with_properties() - * - * \since v0.4.0 - */ -#define AMQP_DEFAULT_HEARTBEAT 0 - -/** - * \def AMQP_DEFAULT_VHOST - * - * Default RabbitMQ vhost: "/" - * - * \sa amqp_login(), amqp_login_with_properties() - * - * \since v0.9.0 - */ -#define AMQP_DEFAULT_VHOST "/" - -/** - * boolean type 0 = false, true otherwise - * - * \since v0.1 - */ -typedef int amqp_boolean_t; - -/** - * Method number - * - * \since v0.1 - */ -typedef uint32_t amqp_method_number_t; - -/** - * Bitmask for flags - * - * \since v0.1 - */ -typedef uint32_t amqp_flags_t; - -/** - * Channel type - * - * \since v0.1 - */ -typedef uint16_t amqp_channel_t; - -/** - * Buffer descriptor - * - * \since v0.1 - */ -typedef struct amqp_bytes_t_ { - size_t len; /**< length of the buffer in bytes */ - void *bytes; /**< pointer to the beginning of the buffer */ -} amqp_bytes_t; - -/** - * Decimal data type - * - * \since v0.1 - */ -typedef struct amqp_decimal_t_ { - uint8_t decimals; /**< the location of the decimal point */ - uint32_t value; /**< the value before the decimal point is applied */ -} amqp_decimal_t; - -/** - * AMQP field table - * - * An AMQP field table is a set of key-value pairs. - * A key is a UTF-8 encoded string up to 128 bytes long, and are not null - * terminated. - * A value can be one of several different datatypes. \sa - * amqp_field_value_kind_t - * - * \sa amqp_table_entry_t - * - * \since v0.1 - */ -typedef struct amqp_table_t_ { - int num_entries; /**< length of entries array */ - struct amqp_table_entry_t_ *entries; /**< an array of table entries */ -} amqp_table_t; - -/** - * An AMQP Field Array - * - * A repeated set of field values, all must be of the same type - * - * \since v0.1 - */ -typedef struct amqp_array_t_ { - int num_entries; /**< Number of entries in the table */ - struct amqp_field_value_t_ *entries; /**< linked list of field values */ -} amqp_array_t; - -/* - 0-9 0-9-1 Qpid/Rabbit Type Remarks ---------------------------------------------------------------------------- - t t Boolean - b b Signed 8-bit - B Unsigned 8-bit - U s Signed 16-bit (A1) - u Unsigned 16-bit - I I I Signed 32-bit - i Unsigned 32-bit - L l Signed 64-bit (B) - l Unsigned 64-bit - f f 32-bit float - d d 64-bit float - D D D Decimal - s Short string (A2) - S S S Long string - A Nested Array - T T T Timestamp (u64) - F F F Nested Table - V V V Void - x Byte array - -Remarks: - - A1, A2: Notice how the types **CONFLICT** here. In Qpid and Rabbit, - 's' means a signed 16-bit integer; in 0-9-1, it means a - short string. - - B: Notice how the signednesses **CONFLICT** here. In Qpid and Rabbit, - 'l' means a signed 64-bit integer; in 0-9-1, it means an unsigned - 64-bit integer. - -I'm going with the Qpid/Rabbit types, where there's a conflict, and -the 0-9-1 types otherwise. 0-8 is a subset of 0-9, which is a subset -of the other two, so this will work for both 0-8 and 0-9-1 branches of -the code. -*/ - -/** - * A field table value - * - * \since v0.1 - */ -typedef struct amqp_field_value_t_ { - uint8_t kind; /**< the type of the entry /sa amqp_field_value_kind_t */ - union { - amqp_boolean_t boolean; /**< boolean type AMQP_FIELD_KIND_BOOLEAN */ - int8_t i8; /**< int8_t type AMQP_FIELD_KIND_I8 */ - uint8_t u8; /**< uint8_t type AMQP_FIELD_KIND_U8 */ - int16_t i16; /**< int16_t type AMQP_FIELD_KIND_I16 */ - uint16_t u16; /**< uint16_t type AMQP_FIELD_KIND_U16 */ - int32_t i32; /**< int32_t type AMQP_FIELD_KIND_I32 */ - uint32_t u32; /**< uint32_t type AMQP_FIELD_KIND_U32 */ - int64_t i64; /**< int64_t type AMQP_FIELD_KIND_I64 */ - uint64_t u64; /**< uint64_t type AMQP_FIELD_KIND_U64, - AMQP_FIELD_KIND_TIMESTAMP */ - float f32; /**< float type AMQP_FIELD_KIND_F32 */ - double f64; /**< double type AMQP_FIELD_KIND_F64 */ - amqp_decimal_t decimal; /**< amqp_decimal_t AMQP_FIELD_KIND_DECIMAL */ - amqp_bytes_t bytes; /**< amqp_bytes_t type AMQP_FIELD_KIND_UTF8, - AMQP_FIELD_KIND_BYTES */ - amqp_table_t table; /**< amqp_table_t type AMQP_FIELD_KIND_TABLE */ - amqp_array_t array; /**< amqp_array_t type AMQP_FIELD_KIND_ARRAY */ - } value; /**< a union of the value */ -} amqp_field_value_t; - -/** - * An entry in a field-table - * - * \sa amqp_table_encode(), amqp_table_decode(), amqp_table_clone() - * - * \since v0.1 - */ -typedef struct amqp_table_entry_t_ { - amqp_bytes_t key; /**< the table entry key. Its a null-terminated UTF-8 - * string, with a maximum size of 128 bytes */ - amqp_field_value_t value; /**< the table entry values */ -} amqp_table_entry_t; - -/** - * Field value types - * - * \since v0.1 - */ -typedef enum { - AMQP_FIELD_KIND_BOOLEAN = - 't', /**< boolean type. 0 = false, 1 = true @see amqp_boolean_t */ - AMQP_FIELD_KIND_I8 = 'b', /**< 8-bit signed integer, datatype: int8_t */ - AMQP_FIELD_KIND_U8 = 'B', /**< 8-bit unsigned integer, datatype: uint8_t */ - AMQP_FIELD_KIND_I16 = 's', /**< 16-bit signed integer, datatype: int16_t */ - AMQP_FIELD_KIND_U16 = 'u', /**< 16-bit unsigned integer, datatype: uint16_t */ - AMQP_FIELD_KIND_I32 = 'I', /**< 32-bit signed integer, datatype: int32_t */ - AMQP_FIELD_KIND_U32 = 'i', /**< 32-bit unsigned integer, datatype: uint32_t */ - AMQP_FIELD_KIND_I64 = 'l', /**< 64-bit signed integer, datatype: int64_t */ - AMQP_FIELD_KIND_U64 = 'L', /**< 64-bit unsigned integer, datatype: uint64_t */ - AMQP_FIELD_KIND_F32 = - 'f', /**< single-precision floating point value, datatype: float */ - AMQP_FIELD_KIND_F64 = - 'd', /**< double-precision floating point value, datatype: double */ - AMQP_FIELD_KIND_DECIMAL = - 'D', /**< amqp-decimal value, datatype: amqp_decimal_t */ - AMQP_FIELD_KIND_UTF8 = 'S', /**< UTF-8 null-terminated character string, - datatype: amqp_bytes_t */ - AMQP_FIELD_KIND_ARRAY = 'A', /**< field array (repeated values of another - datatype. datatype: amqp_array_t */ - AMQP_FIELD_KIND_TIMESTAMP = 'T', /**< 64-bit timestamp. datatype uint64_t */ - AMQP_FIELD_KIND_TABLE = 'F', /**< field table. encapsulates a table inside a - table entry. datatype: amqp_table_t */ - AMQP_FIELD_KIND_VOID = 'V', /**< empty entry */ - AMQP_FIELD_KIND_BYTES = - 'x' /**< unformatted byte string, datatype: amqp_bytes_t */ -} amqp_field_value_kind_t; - -/** - * A list of allocation blocks - * - * \since v0.1 - */ -typedef struct amqp_pool_blocklist_t_ { - int num_blocks; /**< Number of blocks in the block list */ - void **blocklist; /**< Array of memory blocks */ -} amqp_pool_blocklist_t; - -/** - * A memory pool - * - * \since v0.1 - */ -typedef struct amqp_pool_t_ { - size_t pagesize; /**< the size of the page in bytes. Allocations less than or - * equal to this size are allocated in the pages block list. - * Allocations greater than this are allocated in their own - * own block in the large_blocks block list */ - - amqp_pool_blocklist_t pages; /**< blocks that are the size of pagesize */ - amqp_pool_blocklist_t - large_blocks; /**< allocations larger than the pagesize */ - - int next_page; /**< an index to the next unused page block */ - char *alloc_block; /**< pointer to the current allocation block */ - size_t alloc_used; /**< number of bytes in the current allocation block that - has been used */ -} amqp_pool_t; - -/** - * An amqp method - * - * \since v0.1 - */ -typedef struct amqp_method_t_ { - amqp_method_number_t id; /**< the method id number */ - void *decoded; /**< pointer to the decoded method, - * cast to the appropriate type to use */ -} amqp_method_t; - -/** - * An AMQP frame - * - * \since v0.1 - */ -typedef struct amqp_frame_t_ { - uint8_t frame_type; /**< frame type. The types: - * - AMQP_FRAME_METHOD - use the method union member - * - AMQP_FRAME_HEADER - use the properties union member - * - AMQP_FRAME_BODY - use the body_fragment union member - */ - amqp_channel_t channel; /**< the channel the frame was received on */ - union { - amqp_method_t - method; /**< a method, use if frame_type == AMQP_FRAME_METHOD */ - struct { - uint16_t class_id; /**< the class for the properties */ - uint64_t body_size; /**< size of the body in bytes */ - void *decoded; /**< the decoded properties */ - amqp_bytes_t raw; /**< amqp-encoded properties structure */ - } properties; /**< message header, a.k.a., properties, - use if frame_type == AMQP_FRAME_HEADER */ - amqp_bytes_t body_fragment; /**< a body fragment, use if frame_type == - AMQP_FRAME_BODY */ - struct { - uint8_t transport_high; /**< @internal first byte of handshake */ - uint8_t transport_low; /**< @internal second byte of handshake */ - uint8_t protocol_version_major; /**< @internal third byte of handshake */ - uint8_t protocol_version_minor; /**< @internal fourth byte of handshake */ - } protocol_header; /**< Used only when doing the initial handshake with the - broker, don't use otherwise */ - } payload; /**< the payload of the frame */ -} amqp_frame_t; - -/** - * Response type - * - * \since v0.1 - */ -typedef enum amqp_response_type_enum_ { - AMQP_RESPONSE_NONE = 0, /**< the library got an EOF from the socket */ - AMQP_RESPONSE_NORMAL, /**< response normal, the RPC completed successfully */ - AMQP_RESPONSE_LIBRARY_EXCEPTION, /**< library error, an error occurred in the - library, examine the library_error */ - AMQP_RESPONSE_SERVER_EXCEPTION /**< server exception, the broker returned an - error, check replay */ -} amqp_response_type_enum; - -/** - * Reply from a RPC method on the broker - * - * \since v0.1 - */ -typedef struct amqp_rpc_reply_t_ { - amqp_response_type_enum reply_type; /**< the reply type: - * - AMQP_RESPONSE_NORMAL - the RPC - * completed successfully - * - AMQP_RESPONSE_SERVER_EXCEPTION - the - * broker returned - * an exception, check the reply field - * - AMQP_RESPONSE_LIBRARY_EXCEPTION - the - * library - * encountered an error, check the - * library_error field - */ - amqp_method_t reply; /**< in case of AMQP_RESPONSE_SERVER_EXCEPTION this - * field will be set to the method returned from the - * broker */ - int library_error; /**< in case of AMQP_RESPONSE_LIBRARY_EXCEPTION this - * field will be set to an error code. An error - * string can be retrieved using amqp_error_string */ -} amqp_rpc_reply_t; - -/** - * SASL method type - * - * \since v0.1 - */ -typedef enum amqp_sasl_method_enum_ { - AMQP_SASL_METHOD_UNDEFINED = -1, /**< Invalid SASL method */ - AMQP_SASL_METHOD_PLAIN = - 0, /**< the PLAIN SASL method for authentication to the broker */ - AMQP_SASL_METHOD_EXTERNAL = - 1 /**< the EXTERNAL SASL method for authentication to the broker */ -} amqp_sasl_method_enum; - -/** - * connection state object - * - * \since v0.1 - */ -typedef struct amqp_connection_state_t_ *amqp_connection_state_t; - -/** - * Socket object - * - * \since v0.4.0 - */ -typedef struct amqp_socket_t_ amqp_socket_t; - -/** - * Status codes - * - * \since v0.4.0 - */ -/* NOTE: When updating this enum, update the strings in librabbitmq/amqp_api.c - */ -typedef enum amqp_status_enum_ { - AMQP_STATUS_OK = 0x0, /**< Operation successful */ - AMQP_STATUS_NO_MEMORY = -0x0001, /**< Memory allocation - failed */ - AMQP_STATUS_BAD_AMQP_DATA = -0x0002, /**< Incorrect or corrupt - data was received from - the broker. This is a - protocol error. */ - AMQP_STATUS_UNKNOWN_CLASS = -0x0003, /**< An unknown AMQP class - was received. This is - a protocol error. */ - AMQP_STATUS_UNKNOWN_METHOD = -0x0004, /**< An unknown AMQP method - was received. This is - a protocol error. */ - AMQP_STATUS_HOSTNAME_RESOLUTION_FAILED = -0x0005, /**< Unable to resolve the - * hostname */ - AMQP_STATUS_INCOMPATIBLE_AMQP_VERSION = -0x0006, /**< The broker advertised - an incompaible AMQP - version */ - AMQP_STATUS_CONNECTION_CLOSED = -0x0007, /**< The connection to the - broker has been closed - */ - AMQP_STATUS_BAD_URL = -0x0008, /**< malformed AMQP URL */ - AMQP_STATUS_SOCKET_ERROR = -0x0009, /**< A socket error - occurred */ - AMQP_STATUS_INVALID_PARAMETER = -0x000A, /**< An invalid parameter - was passed into the - function */ - AMQP_STATUS_TABLE_TOO_BIG = -0x000B, /**< The amqp_table_t object - cannot be serialized - because the output - buffer is too small */ - AMQP_STATUS_WRONG_METHOD = -0x000C, /**< The wrong method was - received */ - AMQP_STATUS_TIMEOUT = -0x000D, /**< Operation timed out */ - AMQP_STATUS_TIMER_FAILURE = -0x000E, /**< The underlying system - timer facility failed */ - AMQP_STATUS_HEARTBEAT_TIMEOUT = -0x000F, /**< Timed out waiting for - heartbeat */ - AMQP_STATUS_UNEXPECTED_STATE = -0x0010, /**< Unexpected protocol - state */ - AMQP_STATUS_SOCKET_CLOSED = -0x0011, /**< Underlying socket is - closed */ - AMQP_STATUS_SOCKET_INUSE = -0x0012, /**< Underlying socket is - already open */ - AMQP_STATUS_BROKER_UNSUPPORTED_SASL_METHOD = -0x0013, /**< Broker does not - support the requested - SASL mechanism */ - AMQP_STATUS_UNSUPPORTED = -0x0014, /**< Parameter is unsupported - in this version */ - _AMQP_STATUS_NEXT_VALUE = -0x0015, /**< Internal value */ - - AMQP_STATUS_TCP_ERROR = -0x0100, /**< A generic TCP error - occurred */ - AMQP_STATUS_TCP_SOCKETLIB_INIT_ERROR = -0x0101, /**< An error occurred trying - to initialize the - socket library*/ - _AMQP_STATUS_TCP_NEXT_VALUE = -0x0102, /**< Internal value */ - - AMQP_STATUS_SSL_ERROR = -0x0200, /**< A generic SSL error - occurred. */ - AMQP_STATUS_SSL_HOSTNAME_VERIFY_FAILED = -0x0201, /**< SSL validation of - hostname against - peer certificate - failed */ - AMQP_STATUS_SSL_PEER_VERIFY_FAILED = -0x0202, /**< SSL validation of peer - certificate failed. */ - AMQP_STATUS_SSL_CONNECTION_FAILED = -0x0203, /**< SSL handshake failed. */ - _AMQP_STATUS_SSL_NEXT_VALUE = -0x0204 /**< Internal value */ -} amqp_status_enum; - -/** - * AMQP delivery modes. - * Use these values for the #amqp_basic_properties_t::delivery_mode field. - * - * \since v0.5 - */ -typedef enum { - AMQP_DELIVERY_NONPERSISTENT = 1, /**< Non-persistent message */ - AMQP_DELIVERY_PERSISTENT = 2 /**< Persistent message */ -} amqp_delivery_mode_enum; - -AMQP_END_DECLS - -#include - -AMQP_BEGIN_DECLS - -/** - * Empty bytes structure - * - * \since v0.2 - */ -AMQP_PUBLIC_VARIABLE const amqp_bytes_t amqp_empty_bytes; - -/** - * Empty table structure - * - * \since v0.2 - */ -AMQP_PUBLIC_VARIABLE const amqp_table_t amqp_empty_table; - -/** - * Empty table array structure - * - * \since v0.2 - */ -AMQP_PUBLIC_VARIABLE const amqp_array_t amqp_empty_array; - -/* Compatibility macros for the above, to avoid the need to update - code written against earlier versions of librabbitmq. */ - -/** - * \def AMQP_EMPTY_BYTES - * - * Deprecated, use \ref amqp_empty_bytes instead - * - * \deprecated use \ref amqp_empty_bytes instead - * - * \since v0.1 - */ -#define AMQP_EMPTY_BYTES amqp_empty_bytes - -/** - * \def AMQP_EMPTY_TABLE - * - * Deprecated, use \ref amqp_empty_table instead - * - * \deprecated use \ref amqp_empty_table instead - * - * \since v0.1 - */ -#define AMQP_EMPTY_TABLE amqp_empty_table - -/** - * \def AMQP_EMPTY_ARRAY - * - * Deprecated, use \ref amqp_empty_array instead - * - * \deprecated use \ref amqp_empty_array instead - * - * \since v0.1 - */ -#define AMQP_EMPTY_ARRAY amqp_empty_array - -/** - * Initializes an amqp_pool_t memory allocation pool for use - * - * Readies an allocation pool for use. An amqp_pool_t - * must be initialized before use - * - * \param [in] pool the amqp_pool_t structure to initialize. - * Calling this function on a pool a pool that has - * already been initialized will result in undefined - * behavior - * \param [in] pagesize the unit size that the pool will allocate - * memory chunks in. Anything allocated against the pool - * with a requested size will be carved out of a block - * this size. Allocations larger than this will be - * allocated individually - * - * \sa recycle_amqp_pool(), empty_amqp_pool(), amqp_pool_alloc(), - * amqp_pool_alloc_bytes(), amqp_pool_t - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL init_amqp_pool(amqp_pool_t *pool, size_t pagesize); - -/** - * Recycles an amqp_pool_t memory allocation pool - * - * Recycles the space allocate by the pool - * - * This invalidates all allocations made against the pool before this call is - * made, any use of any allocations made before recycle_amqp_pool() is called - * will result in undefined behavior. - * - * Note: this may or may not release memory, to force memory to be released - * call empty_amqp_pool(). - * - * \param [in] pool the amqp_pool_t to recycle - * - * \sa recycle_amqp_pool(), empty_amqp_pool(), amqp_pool_alloc(), - * amqp_pool_alloc_bytes() - * - * \since v0.1 - * - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL recycle_amqp_pool(amqp_pool_t *pool); - -/** - * Empties an amqp memory pool - * - * Releases all memory associated with an allocation pool - * - * \param [in] pool the amqp_pool_t to empty - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL empty_amqp_pool(amqp_pool_t *pool); - -/** - * Allocates a block of memory from an amqp_pool_t memory pool - * - * Memory will be aligned on a 8-byte boundary. If a 0-length allocation is - * requested, a NULL pointer will be returned. - * - * \param [in] pool the allocation pool to allocate the memory from - * \param [in] amount the size of the allocation in bytes. - * \return a pointer to the memory block, or NULL if the allocation cannot - * be satisfied. - * - * \sa init_amqp_pool(), recycle_amqp_pool(), empty_amqp_pool(), - * amqp_pool_alloc_bytes() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void *AMQP_CALL amqp_pool_alloc(amqp_pool_t *pool, size_t amount); - -/** - * Allocates a block of memory from an amqp_pool_t to an amqp_bytes_t - * - * Memory will be aligned on a 8-byte boundary. If a 0-length allocation is - * requested, output.bytes = NULL. - * - * \param [in] pool the allocation pool to allocate the memory from - * \param [in] amount the size of the allocation in bytes - * \param [in] output the location to store the pointer. On success - * output.bytes will be set to the beginning of the buffer - * output.len will be set to amount - * On error output.bytes will be set to NULL and output.len - * set to 0 - * - * \sa init_amqp_pool(), recycle_amqp_pool(), empty_amqp_pool(), - * amqp_pool_alloc() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_pool_alloc_bytes(amqp_pool_t *pool, size_t amount, - amqp_bytes_t *output); - -/** - * Wraps a c string in an amqp_bytes_t - * - * Takes a string, calculates its length and creates an - * amqp_bytes_t that points to it. The string is not duplicated. - * - * For a given input cstr, The amqp_bytes_t output.bytes is the - * same as cstr, output.len is the length of the string not including - * the \0 terminator - * - * This function uses strlen() internally so cstr must be properly - * terminated - * - * \param [in] cstr the c string to wrap - * \return an amqp_bytes_t that describes the string - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_bytes_t AMQP_CALL amqp_cstring_bytes(char const *cstr); - -/** - * Duplicates an amqp_bytes_t buffer. - * - * The buffer is cloned and the contents copied. - * - * The memory associated with the output is allocated - * with amqp_bytes_malloc() and should be freed with - * amqp_bytes_free() - * - * \param [in] src - * \return a clone of the src - * - * \sa amqp_bytes_free(), amqp_bytes_malloc() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_bytes_t AMQP_CALL amqp_bytes_malloc_dup(amqp_bytes_t src); - -/** - * Allocates a amqp_bytes_t buffer - * - * Creates an amqp_bytes_t buffer of the specified amount, the buffer should be - * freed using amqp_bytes_free() - * - * \param [in] amount the size of the buffer in bytes - * \returns an amqp_bytes_t with amount bytes allocated. - * output.bytes will be set to NULL on error - * - * \sa amqp_bytes_free(), amqp_bytes_malloc_dup() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_bytes_t AMQP_CALL amqp_bytes_malloc(size_t amount); - -/** - * Frees an amqp_bytes_t buffer - * - * Frees a buffer allocated with amqp_bytes_malloc() or amqp_bytes_malloc_dup() - * - * Calling amqp_bytes_free on buffers not allocated with one - * of those two functions will result in undefined behavior - * - * \param [in] bytes the buffer to free - * - * \sa amqp_bytes_malloc(), amqp_bytes_malloc_dup() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_bytes_free(amqp_bytes_t bytes); - -/** - * Allocate and initialize a new amqp_connection_state_t object - * - * amqp_connection_state_t objects created with this function - * should be freed with amqp_destroy_connection() - * - * \returns an opaque pointer on success, NULL or 0 on failure. - * - * \sa amqp_destroy_connection() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_connection_state_t AMQP_CALL amqp_new_connection(void); - -/** - * Get the underlying socket descriptor for the connection - * - * \warning Use the socket returned from this function carefully, incorrect use - * of the socket outside of the library will lead to undefined behavior. - * Additionally rabbitmq-c may use the socket differently version-to-version, - * what may work in one version, may break in the next version. Be sure to - * throughly test any applications that use the socket returned by this - * function especially when using a newer version of rabbitmq-c - * - * \param [in] state the connection object - * \returns the socket descriptor if one has been set, -1 otherwise - * - * \sa amqp_tcp_socket_new(), amqp_ssl_socket_new(), amqp_socket_open() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_get_sockfd(amqp_connection_state_t state); - -/** - * Deprecated, use amqp_tcp_socket_new() or amqp_ssl_socket_new() - * - * \deprecated Use amqp_tcp_socket_new() or amqp_ssl_socket_new() - * - * Sets the socket descriptor associated with the connection. The socket - * should be connected to a broker, and should not be read to or written from - * before calling this function. A socket descriptor can be created and opened - * using amqp_open_socket() - * - * \param [in] state the connection object - * \param [in] sockfd the socket - * - * \sa amqp_open_socket(), amqp_tcp_socket_new(), amqp_ssl_socket_new() - * - * \since v0.1 - */ -AMQP_DEPRECATED(AMQP_PUBLIC_FUNCTION void AMQP_CALL - amqp_set_sockfd(amqp_connection_state_t state, int sockfd)); - -/** - * Tune client side parameters - * - * \warning This function may call abort() if the connection is in a certain - * state. As such it should probably not be called code outside the library. - * connection parameters should be specified when calling amqp_login() or - * amqp_login_with_properties() - * - * This function changes channel_max, frame_max, and heartbeat parameters, on - * the client side only. It does not try to renegotiate these parameters with - * the broker. Using this function will lead to unexpected results. - * - * \param [in] state the connection object - * \param [in] channel_max the maximum number of channels. - * The largest this can be is 65535 - * \param [in] frame_max the maximum size of an frame. - * The smallest this can be is 4096 - * The largest this can be is 2147483647 - * Unless you know what you're doing the recommended - * size is 131072 or 128KB - * \param [in] heartbeat the number of seconds between heartbeats - * - * \return AMQP_STATUS_OK on success, an amqp_status_enum value otherwise. - * Possible error codes include: - * - AMQP_STATUS_NO_MEMORY memory allocation failed. - * - AMQP_STATUS_TIMER_FAILURE the underlying system timer indicated it - * failed. - * - * \sa amqp_login(), amqp_login_with_properties() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_tune_connection(amqp_connection_state_t state, - int channel_max, int frame_max, - int heartbeat); - -/** - * Get the maximum number of channels the connection can handle - * - * The maximum number of channels is set when connection negotiation takes - * place in amqp_login() or amqp_login_with_properties(). - * - * \param [in] state the connection object - * \return the maximum number of channels. 0 if there is no limit - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_get_channel_max(amqp_connection_state_t state); - -/** - * Get the maximum size of an frame the connection can handle - * - * The maximum size of an frame is set when connection negotiation takes - * place in amqp_login() or amqp_login_with_properties(). - * - * \param [in] state the connection object - * \return the maximum size of an frame. - * - * \since v0.6 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_get_frame_max(amqp_connection_state_t state); - -/** - * Get the number of seconds between heartbeats of the connection - * - * The number of seconds between heartbeats is set when connection - * negotiation takes place in amqp_login() or amqp_login_with_properties(). - * - * \param [in] state the connection object - * \return the number of seconds between heartbeats. - * - * \since v0.6 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_get_heartbeat(amqp_connection_state_t state); - -/** - * Destroys an amqp_connection_state_t object - * - * Destroys a amqp_connection_state_t object that was created with - * amqp_new_connection(). If the connection with the broker is open, it will be - * implicitly closed with a reply code of 200 (success). Any memory that - * would be freed with amqp_maybe_release_buffers() or - * amqp_maybe_release_buffers_on_channel() will be freed, and use of that - * memory will caused undefined behavior. - * - * \param [in] state the connection object - * \return AMQP_STATUS_OK on success. amqp_status_enum value failure - * - * \sa amqp_new_connection() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_destroy_connection(amqp_connection_state_t state); - -/** - * Process incoming data - * - * \warning This is a low-level function intended for those who want to - * have greater control over input and output over the socket from the - * broker. Correctly using this function requires in-depth knowledge of AMQP - * and rabbitmq-c. - * - * For a given buffer of data received from the broker, decode the first - * frame in the buffer. If more than one frame is contained in the input buffer - * the return value will be less than the received_data size, the caller should - * adjust received_data buffer descriptor to point to the beginning of the - * buffer + the return value. - * - * \param [in] state the connection object - * \param [in] received_data a buffer of data received from the broker. The - * function will return the number of bytes of the buffer it used. The - * function copies these bytes to an internal buffer: this part of the buffer - * may be reused after this function successfully completes. - * \param [in,out] decoded_frame caller should pass in a pointer to an - * amqp_frame_t struct. If there is enough data in received_data for a - * complete frame, decoded_frame->frame_type will be set to something OTHER - * than 0. decoded_frame may contain members pointing to memory owned by - * the state object. This memory can be recycled with - * amqp_maybe_release_buffers() or amqp_maybe_release_buffers_on_channel(). - * \return number of bytes consumed from received_data or 0 if a 0-length - * buffer was passed. A negative return value indicates failure. Possible - * errors: - * - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely - * in an indeterminate state making recovery unlikely. Client should note the - * error and terminate the application - * - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection - * should be shutdown immediately - * - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the - * broker. This is likely a protocol error and the connection should be - * shutdown immediately - * - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class - * was received from the broker. This is likely a protocol error and the - * connection should be shutdown immediately - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_handle_input(amqp_connection_state_t state, - amqp_bytes_t received_data, - amqp_frame_t *decoded_frame); - -/** - * Check to see if connection memory can be released - * - * \deprecated This function is deprecated in favor of - * amqp_maybe_release_buffers() or amqp_maybe_release_buffers_on_channel() - * - * Checks the state of an amqp_connection_state_t object to see if - * amqp_release_buffers() can be called successfully. - * - * \param [in] state the connection object - * \returns TRUE if the buffers can be released FALSE otherwise - * - * \sa amqp_release_buffers() amqp_maybe_release_buffers() - * amqp_maybe_release_buffers_on_channel() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_boolean_t AMQP_CALL amqp_release_buffers_ok(amqp_connection_state_t state); - -/** - * Release amqp_connection_state_t owned memory - * - * \deprecated This function is deprecated in favor of - * amqp_maybe_release_buffers() or amqp_maybe_release_buffers_on_channel() - * - * \warning caller should ensure amqp_release_buffers_ok() returns true before - * calling this function. Failure to do so may result in abort() being called. - * - * Release memory owned by the amqp_connection_state_t for reuse by the - * library. Use of any memory returned by the library before this function is - * called will result in undefined behavior. - * - * \note internally rabbitmq-c tries to reuse memory when possible. As a result - * its possible calling this function may not have a noticeable effect on - * memory usage. - * - * \param [in] state the connection object - * - * \sa amqp_release_buffers_ok() amqp_maybe_release_buffers() - * amqp_maybe_release_buffers_on_channel() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_release_buffers(amqp_connection_state_t state); - -/** - * Release amqp_connection_state_t owned memory - * - * Release memory owned by the amqp_connection_state_t object related to any - * channel, allowing reuse by the library. Use of any memory returned by the - * library before this function is called with result in undefined behavior. - * - * \note internally rabbitmq-c tries to reuse memory when possible. As a result - * its possible calling this function may not have a noticeable effect on - * memory usage. - * - * \param [in] state the connection object - * - * \sa amqp_maybe_release_buffers_on_channel() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_maybe_release_buffers(amqp_connection_state_t state); - -/** - * Release amqp_connection_state_t owned memory related to a channel - * - * Release memory owned by the amqp_connection_state_t object related to the - * specified channel, allowing reuse by the library. Use of any memory returned - * the library for a specific channel will result in undefined behavior. - * - * \note internally rabbitmq-c tries to reuse memory when possible. As a result - * its possible calling this function may not have a noticeable effect on - * memory usage. - * - * \param [in] state the connection object - * \param [in] channel the channel specifier for which memory should be - * released. Note that the library does not care about the state of the - * channel when calling this function - * - * \sa amqp_maybe_release_buffers() - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_maybe_release_buffers_on_channel( - amqp_connection_state_t state, amqp_channel_t channel); - -/** - * Send a frame to the broker - * - * \param [in] state the connection object - * \param [in] frame the frame to send to the broker - * \return AMQP_STATUS_OK on success, an amqp_status_enum value on error. - * Possible error codes: - * - AMQP_STATUS_BAD_AMQP_DATA the serialized form of the method or - * properties was too large to fit in a single AMQP frame, or the - * method contains an invalid value. The frame was not sent. - * - AMQP_STATUS_TABLE_TOO_BIG the serialized form of an amqp_table_t is - * too large to fit in a single AMQP frame. Frame was not sent. - * - AMQP_STATUS_UNKNOWN_METHOD an invalid method type was passed in - * - AMQP_STATUS_UNKNOWN_CLASS an invalid properties type was passed in - * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. The frame - * was sent - * - AMQP_STATUS_SOCKET_ERROR - * - AMQP_STATUS_SSL_ERROR - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_send_frame(amqp_connection_state_t state, - amqp_frame_t const *frame); - -/** - * Compare two table entries - * - * Works just like strcmp(), comparing two the table keys, datatype, then values - * - * \param [in] entry1 the entry on the left - * \param [in] entry2 the entry on the right - * \return 0 if entries are equal, 0 < if left is greater, 0 > if right is - * greater - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_table_entry_cmp(void const *entry1, void const *entry2); - -/** - * Open a socket to a remote host - * - * \deprecated This function is deprecated in favor of amqp_socket_open() - * - * Looks up the hostname, then attempts to open a socket to the host using - * the specified portnumber. It also sets various options on the socket to - * improve performance and correctness. - * - * \param [in] hostname this can be a hostname or IP address. - * Both IPv4 and IPv6 are acceptable - * \param [in] portnumber the port to connect on. RabbitMQ brokers - * listen on port 5672, and 5671 for SSL - * \return a positive value indicates success and is the sockfd. A negative - * value (see amqp_status_enum)is returned on failure. Possible error codes: - * - AMQP_STATUS_TCP_SOCKETLIB_INIT_ERROR Initialization of underlying socket - * library failed. - * - AMQP_STATUS_HOSTNAME_RESOLUTION_FAILED hostname lookup failed. - * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. errno or - * WSAGetLastError() may return more useful information. - * - * \note IPv6 support was added in v0.3 - * - * \sa amqp_socket_open() amqp_set_sockfd() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_open_socket(char const *hostname, int portnumber); - -/** - * Send initial AMQP header to the broker - * - * \warning this is a low level function intended for those who want to - * interact with the broker at a very low level. Use of this function without - * understanding what it does will result in AMQP protocol errors. - * - * This function sends the AMQP protocol header to the broker. - * - * \param [in] state the connection object - * \return AMQP_STATUS_OK on success, a negative value on failure. Possible - * error codes: - * - AMQP_STATUS_CONNECTION_CLOSED the connection to the broker was closed. - * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. It is likely the - * underlying socket has been closed. errno or WSAGetLastError() may provide - * further information. - * - AMQP_STATUS_SSL_ERROR a SSL error occurred. The connection to the broker - * was closed. - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_send_header(amqp_connection_state_t state); - -/** - * Checks to see if there are any incoming frames ready to be read - * - * Checks to see if there are any amqp_frame_t objects buffered by the - * amqp_connection_state_t object. Having one or more frames buffered means - * that amqp_simple_wait_frame() or amqp_simple_wait_frame_noblock() will - * return a frame without potentially blocking on a read() call. - * - * \param [in] state the connection object - * \return TRUE if there are frames enqueued, FALSE otherwise - * - * \sa amqp_simple_wait_frame() amqp_simple_wait_frame_noblock() - * amqp_data_in_buffer() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_boolean_t AMQP_CALL amqp_frames_enqueued(amqp_connection_state_t state); - -/** - * Read a single amqp_frame_t - * - * Waits for the next amqp_frame_t frame to be read from the broker. - * This function has the potential to block for a long time in the case of - * waiting for a basic.deliver method frame from the broker. - * - * The library may buffer frames. When an amqp_connection_state_t object - * has frames buffered calling amqp_simple_wait_frame() will return an - * amqp_frame_t without entering a blocking read(). You can test to see if - * an amqp_connection_state_t object has frames buffered by calling the - * amqp_frames_enqueued() function. - * - * The library has a socket read buffer. When there is data in an - * amqp_connection_state_t read buffer, amqp_simple_wait_frame() may return an - * amqp_frame_t without entering a blocking read(). You can test to see if an - * amqp_connection_state_t object has data in its read buffer by calling the - * amqp_data_in_buffer() function. - * - * \param [in] state the connection object - * \param [out] decoded_frame the frame - * \return AMQP_STATUS_OK on success, an amqp_status_enum value - * is returned otherwise. Possible errors include: - * - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely - * in an indeterminate state making recovery unlikely. Client should note the - * error and terminate the application - * - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection - * should be shutdown immediately - * - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the - * broker. This is likely a protocol error and the connection should be - * shutdown immediately - * - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class - * was received from the broker. This is likely a protocol error and the - * connection should be shutdown immediately - * - AMQP_STATUS_HEARTBEAT_TIMEOUT timed out while waiting for heartbeat - * from the broker. The connection has been closed. - * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. - * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. The connection has - * been closed - * - AMQP_STATUS_SSL_ERROR a SSL socket error occurred. The connection has - * been closed. - * - * \sa amqp_simple_wait_frame_noblock() amqp_frames_enqueued() - * amqp_data_in_buffer() - * - * \note as of v0.4.0 this function will no longer return heartbeat frames - * when enabled by specifying a non-zero heartbeat value in amqp_login(). - * Heartbeating is handled internally by the library. - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_simple_wait_frame(amqp_connection_state_t state, - amqp_frame_t *decoded_frame); - -/** - * Read a single amqp_frame_t with a timeout. - * - * Waits for the next amqp_frame_t frame to be read from the broker, up to - * a timespan specified by tv. The function will return AMQP_STATUS_TIMEOUT - * if the timeout is reached. The tv value is not modified by the function. - * - * If a 0 timeval is specified, the function behaves as if its non-blocking: it - * will test to see if a frame can be read from the broker, and return - * immediately. - * - * If NULL is passed in for tv, the function will behave like - * amqp_simple_wait_frame() and block until a frame is received from the broker - * - * The library may buffer frames. When an amqp_connection_state_t object - * has frames buffered calling amqp_simple_wait_frame_noblock() will return an - * amqp_frame_t without entering a blocking read(). You can test to see if an - * amqp_connection_state_t object has frames buffered by calling the - * amqp_frames_enqueued() function. - * - * The library has a socket read buffer. When there is data in an - * amqp_connection_state_t read buffer, amqp_simple_wait_frame_noblock() may - * return - * an amqp_frame_t without entering a blocking read(). You can test to see if an - * amqp_connection_state_t object has data in its read buffer by calling the - * amqp_data_in_buffer() function. - * - * \note This function does not return heartbeat frames. When enabled, - * heartbeating is handed internally internally by the library. - * - * \param [in,out] state the connection object - * \param [out] decoded_frame the frame - * \param [in] tv the maximum time to wait for a frame to be read. Setting - * tv->tv_sec = 0 and tv->tv_usec = 0 will do a non-blocking read. Specifying - * NULL for tv will make the function block until a frame is read. - * \return AMQP_STATUS_OK on success. An amqp_status_enum value is returned - * otherwise. Possible errors include: - * - AMQP_STATUS_TIMEOUT the timeout was reached while waiting for a frame - * from the broker. - * - AMQP_STATUS_INVALID_PARAMETER the tv parameter contains an invalid value. - * - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely - * in an indeterminate state making recovery unlikely. Client should note the - * error and terminate the application - * - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection - * should be shutdown immediately - * - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the - * broker. This is likely a protocol error and the connection should be - * shutdown immediately - * - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class - * was received from the broker. This is likely a protocol error and the - * connection should be shutdown immediately - * - AMQP_STATUS_HEARTBEAT_TIMEOUT timed out while waiting for heartbeat - * from the broker. The connection has been closed. - * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. - * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. The connection has - * been closed - * - AMQP_STATUS_SSL_ERROR a SSL socket error occurred. The connection has - * been closed. - * - * \sa amqp_simple_wait_frame() amqp_frames_enqueued() amqp_data_in_buffer() - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_simple_wait_frame_noblock(amqp_connection_state_t state, - amqp_frame_t *decoded_frame, - struct timeval *tv); - -/** - * Waits for a specific method from the broker - * - * \warning You probably don't want to use this function. If this function - * doesn't receive exactly the frame requested it closes the whole connection. - * - * Waits for a single method on a channel from the broker. - * If a frame is received that does not match expected_channel - * or expected_method the program will abort - * - * \param [in] state the connection object - * \param [in] expected_channel the channel that the method should be delivered - * on - * \param [in] expected_method the method to wait for - * \param [out] output the method - * \returns AMQP_STATUS_OK on success. An amqp_status_enum value is returned - * otherwise. Possible errors include: - * - AMQP_STATUS_WRONG_METHOD a frame containing the wrong method, wrong frame - * type or wrong channel was received. The connection is closed. - * - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely - * in an indeterminate state making recovery unlikely. Client should note the - * error and terminate the application - * - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection - * should be shutdown immediately - * - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the - * broker. This is likely a protocol error and the connection should be - * shutdown immediately - * - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class - * was received from the broker. This is likely a protocol error and the - * connection should be shutdown immediately - * - AMQP_STATUS_HEARTBEAT_TIMEOUT timed out while waiting for heartbeat - * from the broker. The connection has been closed. - * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. - * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. The connection has - * been closed - * - AMQP_STATUS_SSL_ERROR a SSL socket error occurred. The connection has - * been closed. - * - * \since v0.1 - */ - -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_simple_wait_method(amqp_connection_state_t state, - amqp_channel_t expected_channel, - amqp_method_number_t expected_method, - amqp_method_t *output); - -/** - * Sends a method to the broker - * - * This is a thin wrapper around amqp_send_frame(), providing a way to send - * a method to the broker on a specified channel. - * - * \param [in] state the connection object - * \param [in] channel the channel object - * \param [in] id the method number - * \param [in] decoded the method object - * \returns AMQP_STATUS_OK on success, an amqp_status_enum value otherwise. - * Possible errors include: - * - AMQP_STATUS_BAD_AMQP_DATA the serialized form of the method or - * properties was too large to fit in a single AMQP frame, or the - * method contains an invalid value. The frame was not sent. - * - AMQP_STATUS_TABLE_TOO_BIG the serialized form of an amqp_table_t is - * too large to fit in a single AMQP frame. Frame was not sent. - * - AMQP_STATUS_UNKNOWN_METHOD an invalid method type was passed in - * - AMQP_STATUS_UNKNOWN_CLASS an invalid properties type was passed in - * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. The frame - * was sent - * - AMQP_STATUS_SOCKET_ERROR - * - AMQP_STATUS_SSL_ERROR - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_send_method(amqp_connection_state_t state, - amqp_channel_t channel, amqp_method_number_t id, - void *decoded); - -/** - * Sends a method to the broker and waits for a method response - * - * \param [in] state the connection object - * \param [in] channel the channel object - * \param [in] request_id the method number of the request - * \param [in] expected_reply_ids a 0 terminated array of expected response - * method numbers - * \param [in] decoded_request_method the method to be sent to the broker - * \return a amqp_rpc_reply_t: - * - r.reply_type == AMQP_RESPONSE_NORMAL. RPC completed successfully - * - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an - * exception: - * - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception - * occurred, cast r.reply.decoded to amqp_channel_close_t* to see details - * of the exception. The client should amqp_send_method() a - * amqp_channel_close_ok_t. The channel must be re-opened before it - * can be used again. Any resources associated with the channel - * (auto-delete exchanges, auto-delete queues, consumers) are invalid - * and must be recreated before attempting to use them again. - * - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception - * occurred, cast r.reply.decoded to amqp_connection_close_t* to see - * details of the exception. The client amqp_send_method() a - * amqp_connection_close_ok_t and disconnect from the broker. - * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. An exception occurred - * within the library. Examine r.library_error and compare it against - * amqp_status_enum values to determine the error. - * - * \sa amqp_simple_rpc_decoded() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_simple_rpc( - amqp_connection_state_t state, amqp_channel_t channel, - amqp_method_number_t request_id, amqp_method_number_t *expected_reply_ids, - void *decoded_request_method); - -/** - * Sends a method to the broker and waits for a method response - * - * \param [in] state the connection object - * \param [in] channel the channel object - * \param [in] request_id the method number of the request - * \param [in] reply_id the method number expected in response - * \param [in] decoded_request_method the request method - * \return a pointer to the method returned from the broker, or NULL on error. - * On error amqp_get_rpc_reply() will return an amqp_rpc_reply_t with - * details on the error that occurred. - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -void *AMQP_CALL amqp_simple_rpc_decoded(amqp_connection_state_t state, - amqp_channel_t channel, - amqp_method_number_t request_id, - amqp_method_number_t reply_id, - void *decoded_request_method); - -/** - * Get the last global amqp_rpc_reply - * - * The API methods corresponding to most synchronous AMQP methods - * return a pointer to the decoded method result. Upon error, they - * return NULL, and we need some way of discovering what, if anything, - * went wrong. amqp_get_rpc_reply() returns the most recent - * amqp_rpc_reply_t instance corresponding to such an API operation - * for the given connection. - * - * Only use it for operations that do not themselves return - * amqp_rpc_reply_t; operations that do return amqp_rpc_reply_t - * generally do NOT update this per-connection-global amqp_rpc_reply_t - * instance. - * - * \param [in] state the connection object - * \return the most recent amqp_rpc_reply_t: - * - r.reply_type == AMQP_RESPONSE_NORMAL. RPC completed successfully - * - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an - * exception: - * - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception - * occurred, cast r.reply.decoded to amqp_channel_close_t* to see details - * of the exception. The client should amqp_send_method() a - * amqp_channel_close_ok_t. The channel must be re-opened before it - * can be used again. Any resources associated with the channel - * (auto-delete exchanges, auto-delete queues, consumers) are invalid - * and must be recreated before attempting to use them again. - * - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception - * occurred, cast r.reply.decoded to amqp_connection_close_t* to see - * details of the exception. The client amqp_send_method() a - * amqp_connection_close_ok_t and disconnect from the broker. - * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. An exception occurred - * within the library. Examine r.library_error and compare it against - * amqp_status_enum values to determine the error. - * - * \sa amqp_simple_rpc_decoded() - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_get_rpc_reply(amqp_connection_state_t state); - -/** - * Login to the broker - * - * After using amqp_open_socket and amqp_set_sockfd, call - * amqp_login to complete connecting to the broker - * - * \param [in] state the connection object - * \param [in] vhost the virtual host to connect to on the broker. The default - * on most brokers is "/" - * \param [in] channel_max the limit for number of channels for the connection. - * 0 means no limit, and is a good default - * (AMQP_DEFAULT_MAX_CHANNELS) - * Note that the maximum number of channels the protocol supports - * is 65535 (2^16, with the 0-channel reserved). The server can - * set a lower channel_max and then the client will use the lowest - * of the two - * \param [in] frame_max the maximum size of an AMQP frame on the wire to - * request of the broker for this connection. 4096 is the minimum - * size, 2^31-1 is the maximum, a good default is 131072 (128KB), - * or AMQP_DEFAULT_FRAME_SIZE - * \param [in] heartbeat the number of seconds between heartbeat frames to - * request of the broker. A value of 0 disables heartbeats. - * Note rabbitmq-c only has partial support for heartbeats, as of - * v0.4.0 they are only serviced during amqp_basic_publish() and - * amqp_simple_wait_frame()/amqp_simple_wait_frame_noblock() - * \param [in] sasl_method the SASL method to authenticate with the broker. - * followed by the authentication information. The following SASL - * methods are implemented: - * - AMQP_SASL_METHOD_PLAIN, the AMQP_SASL_METHOD_PLAIN argument - * should be followed by two arguments in this order: - * const char* username, and const char* password. - * - AMQP_SASL_METHOD_EXTERNAL, the AMQP_SASL_METHOD_EXTERNAL - * argument should be followed one argument: - * const char* identity. - * \return amqp_rpc_reply_t indicating success or failure. - * - r.reply_type == AMQP_RESPONSE_NORMAL. Login completed successfully - * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. In most cases errors - * from the broker when logging in will be represented by the broker closing - * the socket. In this case r.library_error will be set to - * AMQP_STATUS_CONNECTION_CLOSED. This error can represent a number of - * error conditions including: invalid vhost, authentication failure. - * - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an - * exception: - * - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception - * occurred, cast r.reply.decoded to amqp_channel_close_t* to see details - * of the exception. The client should amqp_send_method() a - * amqp_channel_close_ok_t. The channel must be re-opened before it - * can be used again. Any resources associated with the channel - * (auto-delete exchanges, auto-delete queues, consumers) are invalid - * and must be recreated before attempting to use them again. - * - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception - * occurred, cast r.reply.decoded to amqp_connection_close_t* to see - * details of the exception. The client amqp_send_method() a - * amqp_connection_close_ok_t and disconnect from the broker. - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_login(amqp_connection_state_t state, - char const *vhost, int channel_max, - int frame_max, int heartbeat, - amqp_sasl_method_enum sasl_method, ...); - -/** - * Login to the broker passing a properties table - * - * This function is similar to amqp_login() and differs in that it provides a - * way to pass client properties to the broker. This is commonly used to - * negotiate newer protocol features as they are supported by the broker. - * - * \param [in] state the connection object - * \param [in] vhost the virtual host to connect to on the broker. The default - * on most brokers is "/" - * \param [in] channel_max the limit for the number of channels for the - * connection. - * 0 means no limit, and is a good default - * (AMQP_DEFAULT_MAX_CHANNELS) - * Note that the maximum number of channels the protocol supports - * is 65535 (2^16, with the 0-channel reserved). The server can - * set a lower channel_max and then the client will use the lowest - * of the two - * \param [in] frame_max the maximum size of an AMQP frame ont he wire to - * request of the broker for this connection. 4096 is the minimum - * size, 2^31-1 is the maximum, a good default is 131072 (128KB), - * or AMQP_DEFAULT_FRAME_SIZE - * \param [in] heartbeat the number of seconds between heartbeat frame to - * request of the broker. A value of 0 disables heartbeats. - * Note rabbitmq-c only has partial support for hearts, as of - * v0.4.0 heartbeats are only serviced during amqp_basic_publish(), - * and amqp_simple_wait_frame()/amqp_simple_wait_frame_noblock() - * \param [in] properties a table of properties to send the broker. - * \param [in] sasl_method the SASL method to authenticate with the broker - * followed by the authentication information. The following SASL - * methods are implemented: - * - AMQP_SASL_METHOD_PLAIN, the AMQP_SASL_METHOD_PLAIN argument - * should be followed by two arguments in this order: - * const char* username, and const char* password. - * - AMQP_SASL_METHOD_EXTERNAL, the AMQP_SASL_METHOD_EXTERNAL - * argument should be followed one argument: - * const char* identity. - * \return amqp_rpc_reply_t indicating success or failure. - * - r.reply_type == AMQP_RESPONSE_NORMAL. Login completed successfully - * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. In most cases errors - * from the broker when logging in will be represented by the broker closing - * the socket. In this case r.library_error will be set to - * AMQP_STATUS_CONNECTION_CLOSED. This error can represent a number of - * error conditions including: invalid vhost, authentication failure. - * - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an - * exception: - * - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception - * occurred, cast r.reply.decoded to amqp_channel_close_t* to see details - * of the exception. The client should amqp_send_method() a - * amqp_channel_close_ok_t. The channel must be re-opened before it - * can be used again. Any resources associated with the channel - * (auto-delete exchanges, auto-delete queues, consumers) are invalid - * and must be recreated before attempting to use them again. - * - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception - * occurred, cast r.reply.decoded to amqp_connection_close_t* to see - * details of the exception. The client amqp_send_method() a - * amqp_connection_close_ok_t and disconnect from the broker. - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_login_with_properties( - amqp_connection_state_t state, char const *vhost, int channel_max, - int frame_max, int heartbeat, const amqp_table_t *properties, - amqp_sasl_method_enum sasl_method, ...); - -struct amqp_basic_properties_t_; - -/** - * Publish a message to the broker - * - * Publish a message on an exchange with a routing key. - * - * Note that at the AMQ protocol level basic.publish is an async method: - * this means error conditions that occur on the broker (such as publishing to - * a non-existent exchange) will not be reflected in the return value of this - * function. - * - * \param [in] state the connection object - * \param [in] channel the channel identifier - * \param [in] exchange the exchange on the broker to publish to - * \param [in] routing_key the routing key to use when publishing the message - * \param [in] mandatory indicate to the broker that the message MUST be routed - * to a queue. If the broker cannot do this it should respond with - * a basic.return method. - * \param [in] immediate indicate to the broker that the message MUST be - * delivered to a consumer immediately. If the broker cannot do this - * it should respond with a basic.return method. - * \param [in] properties the properties associated with the message - * \param [in] body the message body - * \return AMQP_STATUS_OK on success, amqp_status_enum value on failure. Note - * that basic.publish is an async method, the return value from this - * function only indicates that the message data was successfully - * transmitted to the broker. It does not indicate failures that occur - * on the broker, such as publishing to a non-existent exchange. - * Possible error values: - * - AMQP_STATUS_TIMER_FAILURE: system timer facility returned an error - * the message was not sent. - * - AMQP_STATUS_HEARTBEAT_TIMEOUT: connection timed out waiting for a - * heartbeat from the broker. The message was not sent. - * - AMQP_STATUS_NO_MEMORY: memory allocation failed. The message was - * not sent. - * - AMQP_STATUS_TABLE_TOO_BIG: a table in the properties was too large - * to fit in a single frame. Message was not sent. - * - AMQP_STATUS_CONNECTION_CLOSED: the connection was closed. - * - AMQP_STATUS_SSL_ERROR: a SSL error occurred. - * - AMQP_STATUS_TCP_ERROR: a TCP error occurred. errno or - * WSAGetLastError() may provide more information - * - * Note: this function does heartbeat processing as of v0.4.0 - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_basic_publish( - amqp_connection_state_t state, amqp_channel_t channel, - amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_boolean_t mandatory, - amqp_boolean_t immediate, struct amqp_basic_properties_t_ const *properties, - amqp_bytes_t body); - -/** - * Closes an channel - * - * \param [in] state the connection object - * \param [in] channel the channel identifier - * \param [in] code the reason for closing the channel, AMQP_REPLY_SUCCESS is a - * good default - * \return amqp_rpc_reply_t indicating success or failure - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_channel_close(amqp_connection_state_t state, - amqp_channel_t channel, int code); - -/** - * Closes the entire connection - * - * Implicitly closes all channels and informs the broker the connection - * is being closed, after receiving acknowledgment from the broker it closes - * the socket. - * - * \param [in] state the connection object - * \param [in] code the reason code for closing the connection. - * AMQP_REPLY_SUCCESS is a good default. - * \return amqp_rpc_reply_t indicating the result - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_connection_close(amqp_connection_state_t state, - int code); - -/** - * Acknowledges a message - * - * Does a basic.ack on a received message - * - * \param [in] state the connection object - * \param [in] channel the channel identifier - * \param [in] delivery_tag the delivery tag of the message to be ack'd - * \param [in] multiple if true, ack all messages up to this delivery tag, if - * false ack only this delivery tag - * \return 0 on success, 0 > on failing to send the ack to the broker. - * this will not indicate failure if something goes wrong on the - * broker - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_basic_ack(amqp_connection_state_t state, - amqp_channel_t channel, uint64_t delivery_tag, - amqp_boolean_t multiple); - -/** - * Do a basic.get - * - * Synchonously polls the broker for a message in a queue, and - * retrieves the message if a message is in the queue. - * - * \param [in] state the connection object - * \param [in] channel the channel identifier to use - * \param [in] queue the queue name to retrieve from - * \param [in] no_ack if true the message is automatically ack'ed - * if false amqp_basic_ack should be called once the message - * retrieved has been processed - * \return amqp_rpc_reply indicating success or failure - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_basic_get(amqp_connection_state_t state, - amqp_channel_t channel, - amqp_bytes_t queue, - amqp_boolean_t no_ack); - -/** - * Do a basic.reject - * - * Actively reject a message that has been delivered - * - * \param [in] state the connection object - * \param [in] channel the channel identifier - * \param [in] delivery_tag the delivery tag of the message to reject - * \param [in] requeue indicate to the broker whether it should requeue the - * message or just discard it. - * \return 0 on success, 0 > on failing to send the reject method to the broker. - * This will not indicate failure if something goes wrong on the - * broker. - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_basic_reject(amqp_connection_state_t state, - amqp_channel_t channel, uint64_t delivery_tag, - amqp_boolean_t requeue); - -/** - * Do a basic.nack - * - * Actively reject a message, this has the same effect as amqp_basic_reject() - * however, amqp_basic_nack() can negatively acknowledge multiple messages with - * one call much like amqp_basic_ack() can acknowledge mutliple messages with - * one call. - * - * \param [in] state the connection object - * \param [in] channel the channel identifier - * \param [in] delivery_tag the delivery tag of the message to reject - * \param [in] multiple if set to 1 negatively acknowledge all unacknowledged - * messages on this channel. - * \param [in] requeue indicate to the broker whether it should requeue the - * message or dead-letter it. - * \return AMQP_STATUS_OK on success, an amqp_status_enum value otherwise. - * - * \since v0.5.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_basic_nack(amqp_connection_state_t state, - amqp_channel_t channel, uint64_t delivery_tag, - amqp_boolean_t multiple, amqp_boolean_t requeue); -/** - * Check to see if there is data left in the receive buffer - * - * Can be used to see if there is data still in the buffer, if so - * calling amqp_simple_wait_frame will not immediately enter a - * blocking read. - * - * \param [in] state the connection object - * \return true if there is data in the recieve buffer, false otherwise - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -amqp_boolean_t AMQP_CALL amqp_data_in_buffer(amqp_connection_state_t state); - -/** - * Get the error string for the given error code. - * - * \deprecated This function has been deprecated in favor of - * \ref amqp_error_string2() which returns statically allocated - * string which do not need to be freed by the caller. - * - * The returned string resides on the heap; the caller is responsible - * for freeing it. - * - * \param [in] err return error code - * \return the error string - * - * \since v0.1 - */ -AMQP_DEPRECATED( - AMQP_PUBLIC_FUNCTION char *AMQP_CALL amqp_error_string(int err)); - -/** - * Get the error string for the given error code. - * - * Get an error string associated with an error code. The string is statically - * allocated and does not need to be freed - * - * \param [in] err the error code - * \return the error string - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -const char *AMQP_CALL amqp_error_string2(int err); - -/** - * Deserialize an amqp_table_t from AMQP wireformat - * - * This is an internal function and is not typically used by - * client applications - * - * \param [in] encoded the buffer containing the serialized data - * \param [in] pool memory pool used to allocate the table entries from - * \param [in] output the amqp_table_t structure to fill in. Any existing - * entries will be erased - * \param [in,out] offset The offset into the encoded buffer to start - * reading the serialized table. It will be updated - * by this function to end of the table - * \return AMQP_STATUS_OK on success, an amqp_status_enum value on failure - * Possible error codes: - * - AMQP_STATUS_NO_MEMORY out of memory - * - AMQP_STATUS_BAD_AMQP_DATA invalid wireformat - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_decode_table(amqp_bytes_t encoded, amqp_pool_t *pool, - amqp_table_t *output, size_t *offset); - -/** - * Serializes an amqp_table_t to the AMQP wireformat - * - * This is an internal function and is not typically used by - * client applications - * - * \param [in] encoded the buffer where to serialize the table to - * \param [in] input the amqp_table_t to serialize - * \param [in,out] offset The offset into the encoded buffer to start - * writing the serialized table. It will be updated - * by this function to where writing left off - * \return AMQP_STATUS_OK on success, an amqp_status_enum value on failure - * Possible error codes: - * - AMQP_STATUS_TABLE_TOO_BIG the serialized form is too large for the - * buffer - * - AMQP_STATUS_BAD_AMQP_DATA invalid table - * - * \since v0.1 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_encode_table(amqp_bytes_t encoded, amqp_table_t *input, - size_t *offset); - -/** - * Create a deep-copy of an amqp_table_t object - * - * Creates a deep-copy of an amqp_table_t object, using the provided pool - * object to allocate the necessary memory. This memory can be freed later by - * call recycle_amqp_pool(), or empty_amqp_pool() - * - * \param [in] original the table to copy - * \param [in,out] clone the table to copy to - * \param [in] pool the initialized memory pool to do allocations for the table - * from - * \return AMQP_STATUS_OK on success, amqp_status_enum value on failure. - * Possible error values: - * - AMQP_STATUS_NO_MEMORY - memory allocation failure. - * - AMQP_STATUS_INVALID_PARAMETER - invalid table (e.g., no key name) - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_table_clone(const amqp_table_t *original, - amqp_table_t *clone, amqp_pool_t *pool); - -/** - * A message object - * - * \since v0.4.0 - */ -typedef struct amqp_message_t_ { - amqp_basic_properties_t properties; /**< message properties */ - amqp_bytes_t body; /**< message body */ - amqp_pool_t pool; /**< pool used to allocate properties */ -} amqp_message_t; - -/** - * Reads the next message on a channel - * - * Reads a complete message (header + body) on a specified channel. This - * function is intended to be used with amqp_basic_get() or when an - * AMQP_BASIC_DELIVERY_METHOD method is received. - * - * \param [in,out] state the connection object - * \param [in] channel the channel on which to read the message from - * \param [in,out] message a pointer to a amqp_message_t object. Caller should - * call amqp_message_destroy() when it is done using the - * fields in the message object. The caller is responsible for - * allocating/destroying the amqp_message_t object itself. - * \param [in] flags pass in 0. Currently unused. - * \returns a amqp_rpc_reply_t object. ret.reply_type == AMQP_RESPONSE_NORMAL on - * success. - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_read_message(amqp_connection_state_t state, - amqp_channel_t channel, - amqp_message_t *message, - int flags); - -/** - * Frees memory associated with a amqp_message_t allocated in amqp_read_message - * - * \param [in] message - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_destroy_message(amqp_message_t *message); - -/** - * Envelope object - * - * \since v0.4.0 - */ -typedef struct amqp_envelope_t_ { - amqp_channel_t channel; /**< channel message was delivered on */ - amqp_bytes_t - consumer_tag; /**< the consumer tag the message was delivered to */ - uint64_t delivery_tag; /**< the messages delivery tag */ - amqp_boolean_t redelivered; /**< flag indicating whether this message is being - redelivered */ - amqp_bytes_t exchange; /**< exchange this message was published to */ - amqp_bytes_t - routing_key; /**< the routing key this message was published with */ - amqp_message_t message; /**< the message */ -} amqp_envelope_t; - -/** - * Wait for and consume a message - * - * Waits for a basic.deliver method on any channel, upon receipt of - * basic.deliver it reads that message, and returns. If any other method is - * received before basic.deliver, this function will return an amqp_rpc_reply_t - * with ret.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION, and - * ret.library_error == AMQP_STATUS_UNEXPECTED_STATE. The caller should then - * call amqp_simple_wait_frame() to read this frame and take appropriate action. - * - * This function should be used after starting a consumer with the - * amqp_basic_consume() function - * - * \param [in,out] state the connection object - * \param [in,out] envelope a pointer to a amqp_envelope_t object. Caller - * should call #amqp_destroy_envelope() when it is done using - * the fields in the envelope object. The caller is responsible - * for allocating/destroying the amqp_envelope_t object itself. - * \param [in] timeout a timeout to wait for a message delivery. Passing in - * NULL will result in blocking behavior. - * \param [in] flags pass in 0. Currently unused. - * \returns a amqp_rpc_reply_t object. ret.reply_type == AMQP_RESPONSE_NORMAL - * on success. If ret.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION, - * and ret.library_error == AMQP_STATUS_UNEXPECTED_STATE, a frame other - * than AMQP_BASIC_DELIVER_METHOD was received, the caller should call - * amqp_simple_wait_frame() to read this frame and take appropriate - * action. - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_rpc_reply_t AMQP_CALL amqp_consume_message(amqp_connection_state_t state, - amqp_envelope_t *envelope, - struct timeval *timeout, - int flags); - -/** - * Frees memory associated with a amqp_envelope_t allocated in - * amqp_consume_message() - * - * \param [in] envelope - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_destroy_envelope(amqp_envelope_t *envelope); - -/** - * Parameters used to connect to the RabbitMQ broker - * - * \since v0.2 - */ -struct amqp_connection_info { - char *user; /**< the username to authenticate with the broker, default on most - broker is 'guest' */ - char *password; /**< the password to authenticate with the broker, default on - most brokers is 'guest' */ - char *host; /**< the hostname of the broker */ - char *vhost; /**< the virtual host on the broker to connect to, a good default - is "/" */ - int port; /**< the port that the broker is listening on, default on most - brokers is 5672 */ - amqp_boolean_t ssl; -}; - -/** - * Initialze an amqp_connection_info to default values - * - * The default values are: - * - user: "guest" - * - password: "guest" - * - host: "localhost" - * - vhost: "/" - * - port: 5672 - * - * \param [out] parsed the connection info to set defaults on - * - * \since v0.2 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL - amqp_default_connection_info(struct amqp_connection_info *parsed); - -/** - * Parse a connection URL - * - * An amqp connection url takes the form: - * - * amqp://[$USERNAME[:$PASSWORD]\@]$HOST[:$PORT]/[$VHOST] - * - * Examples: - * amqp://guest:guest\@localhost:5672// - * amqp://guest:guest\@localhost/myvhost - * - * Any missing parts of the URL will be set to the defaults specified in - * amqp_default_connection_info. For amqps: URLs the default port will be set - * to 5671 instead of 5672 for non-SSL URLs. - * - * \note This function modifies url parameter. - * - * \param [in] url URI to parse, note that this parameter is modified by the - * function. - * \param [out] parsed the connection info gleaned from the URI. The char* - * members will point to parts of the url input parameter. - * Memory management will depend on how the url is allocated. - * \returns AMQP_STATUS_OK on success, AMQP_STATUS_BAD_URL on failure - * - * \since v0.2 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_parse_url(char *url, struct amqp_connection_info *parsed); - -/* socket API */ - -/** - * Open a socket connection. - * - * This function opens a socket connection returned from amqp_tcp_socket_new() - * or amqp_ssl_socket_new(). This function should be called after setting - * socket options and prior to assigning the socket to an AMQP connection with - * amqp_set_socket(). - * - * \param [in,out] self A socket object. - * \param [in] host Connect to this host. - * \param [in] port Connect on this remote port. - * - * \return AMQP_STATUS_OK on success, an amqp_status_enum on failure - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_socket_open(amqp_socket_t *self, const char *host, int port); - -/** - * Open a socket connection. - * - * This function opens a socket connection returned from amqp_tcp_socket_new() - * or amqp_ssl_socket_new(). This function should be called after setting - * socket options and prior to assigning the socket to an AMQP connection with - * amqp_set_socket(). - * - * \param [in,out] self A socket object. - * \param [in] host Connect to this host. - * \param [in] port Connect on this remote port. - * \param [in] timeout Max allowed time to spent on opening. If NULL - run in - * blocking mode - * - * \return AMQP_STATUS_OK on success, an amqp_status_enum on failure. - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_socket_open_noblock(amqp_socket_t *self, const char *host, - int port, struct timeval *timeout); - -/** - * Get the socket descriptor in use by a socket object. - * - * Retrieve the underlying socket descriptor. This function can be used to - * perform low-level socket operations that aren't supported by the socket - * interface. Use with caution! - * - * \param [in,out] self A socket object. - * - * \return The underlying socket descriptor, or -1 if there is no socket - * descriptor associated with - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_socket_get_sockfd(amqp_socket_t *self); - -/** - * Get the socket object associated with a amqp_connection_state_t - * - * \param [in] state the connection object to get the socket from - * \return a pointer to the socket object, or NULL if one has not been assigned - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_socket_t *AMQP_CALL amqp_get_socket(amqp_connection_state_t state); - -/** - * Get the broker properties table - * - * \param [in] state the connection object - * \return a pointer to an amqp_table_t containing the properties advertised - * by the broker on connection. The connection object owns the table, it - * should not be modified. - * - * \since v0.5.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_table_t *AMQP_CALL - amqp_get_server_properties(amqp_connection_state_t state); - -/** - * Get the client properties table - * - * Get the properties that were passed to the broker on connection. - * - * \param [in] state the connection object - * \return a pointer to an amqp_table_t containing the properties advertised - * by the client on connection. The connection object owns the table, it - * should not be modified. - * - * \since v0.7.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_table_t *AMQP_CALL - amqp_get_client_properties(amqp_connection_state_t state); - -/** - * Get the login handshake timeout. - * - * amqp_login and amqp_login_with_properties perform the login handshake with - * the broker. This function returns the timeout associated with completing - * this operation from the client side. This value can be set by using the - * amqp_set_handshake_timeout. - * - * Note that the RabbitMQ broker has configurable timeout for completing the - * login handshake, the default is 10 seconds. rabbitmq-c has a default of 12 - * seconds. - * - * \param [in] state the connection object - * \return a struct timeval representing the current login timeout for the state - * object. A NULL value represents an infinite timeout. The memory returned is - * owned by the connection object. - * - * \since v0.9.0 - */ -AMQP_PUBLIC_FUNCTION -struct timeval *AMQP_CALL - amqp_get_handshake_timeout(amqp_connection_state_t state); - -/** - * Set the login handshake timeout. - * - * amqp_login and amqp_login_with_properties perform the login handshake with - * the broker. This function sets the timeout associated with completing this - * operation from the client side. - * - * The timeout must be set before amqp_login or amqp_login_with_properties is - * called to change from the default timeout. - * - * Note that the RabbitMQ broker has a configurable timeout for completing the - * login handshake, the default is 10 seconds. rabbitmq-c has a default of 12 - * seconds. - * - * \param [in] state the connection object - * \param [in] timeout a struct timeval* representing new login timeout for the - * state object. NULL represents an infinite timeout. The value of timeout is - * copied internally, the caller is responsible for ownership of the passed in - * pointer, it does not need to remain valid after this function is called. - * \return AMQP_STATUS_OK on success. - * - * \since v0.9.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_set_handshake_timeout(amqp_connection_state_t state, - struct timeval *timeout); - -/** - * Get the RPC timeout - * - * Gets the timeout for any RPC-style AMQP command (e.g., amqp_queue_declare). - * This timeout may be changed at any time by calling \amqp_set_rpc_timeout - * function with a new timeout. The timeout applies individually to each RPC - * that is made. - * - * The default value is NULL, or an infinite timeout. - * - * When an RPC times out, the function will return an error AMQP_STATUS_TIMEOUT, - * and the connection will be closed. - * - *\warning RPC-timeouts are an advanced feature intended to be used to detect - * dead connections quickly when the rabbitmq-c implementation of heartbeats - * does not work. Do not use RPC timeouts unless you understand the implications - * of doing so. - * - * \param [in] state the connection object - * \return a struct timeval representing the current RPC timeout for the state - * object. A NULL value represents an infinite timeout. The memory returned is - * owned by the connection object. - * - * \since v0.9.0 - */ -AMQP_PUBLIC_FUNCTION -struct timeval *AMQP_CALL amqp_get_rpc_timeout(amqp_connection_state_t state); - -/** - * Set the RPC timeout - * - * Sets the timeout for any RPC-style AMQP command (e.g., amqp_queue_declare). - * This timeout may be changed at any time by calling this function with a new - * timeout. The timeout applies individually to each RPC that is made. - * - * The default value is NULL, or an infinite timeout. - * - * When an RPC times out, the function will return an error AMQP_STATUS_TIMEOUT, - * and the connection will be closed. - * - *\warning RPC-timeouts are an advanced feature intended to be used to detect - * dead connections quickly when the rabbitmq-c implementation of heartbeats - * does not work. Do not use RPC timeouts unless you understand the implications - * of doing so. - * - * \param [in] state the connection object - * \param [in] timeout a struct timeval* representing new RPC timeout for the - * state object. NULL represents an infinite timeout. The value of timeout is - * copied internally, the caller is responsible for ownership of the passed - * pointer, it does not need to remain valid after this function is called. - * \return AMQP_STATUS_SUCCESS on success. - * - * \since v0.9.0 - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_set_rpc_timeout(amqp_connection_state_t state, - struct timeval *timeout); - -AMQP_END_DECLS - -#endif /* AMQP_H */ diff --git a/ext/librabbitmq/macos/include/amqp_framing.h b/ext/librabbitmq/macos/include/amqp_framing.h deleted file mode 100644 index fb20acc1f..000000000 --- a/ext/librabbitmq/macos/include/amqp_framing.h +++ /dev/null @@ -1,1144 +0,0 @@ -/* Generated code. Do not edit. Edit and re-run codegen.py instead. - * - * ***** BEGIN LICENSE BLOCK ***** - * Version: MIT - * - * Portions created by Alan Antonuk are Copyright (c) 2012-2013 - * Alan Antonuk. All Rights Reserved. - * - * Portions created by VMware are Copyright (c) 2007-2012 VMware, Inc. - * All Rights Reserved. - * - * Portions created by Tony Garnock-Jones are Copyright (c) 2009-2010 - * VMware, Inc. and Tony Garnock-Jones. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * ***** END LICENSE BLOCK ***** - */ - -/** @file amqp_framing.h */ -#ifndef AMQP_FRAMING_H -#define AMQP_FRAMING_H - -#include - -AMQP_BEGIN_DECLS - -#define AMQP_PROTOCOL_VERSION_MAJOR 0 /**< AMQP protocol version major */ -#define AMQP_PROTOCOL_VERSION_MINOR 9 /**< AMQP protocol version minor */ -#define AMQP_PROTOCOL_VERSION_REVISION \ - 1 /**< AMQP protocol version revision \ - */ -#define AMQP_PROTOCOL_PORT 5672 /**< Default AMQP Port */ -#define AMQP_FRAME_METHOD 1 /**< Constant: FRAME-METHOD */ -#define AMQP_FRAME_HEADER 2 /**< Constant: FRAME-HEADER */ -#define AMQP_FRAME_BODY 3 /**< Constant: FRAME-BODY */ -#define AMQP_FRAME_HEARTBEAT 8 /**< Constant: FRAME-HEARTBEAT */ -#define AMQP_FRAME_MIN_SIZE 4096 /**< Constant: FRAME-MIN-SIZE */ -#define AMQP_FRAME_END 206 /**< Constant: FRAME-END */ -#define AMQP_REPLY_SUCCESS 200 /**< Constant: REPLY-SUCCESS */ -#define AMQP_CONTENT_TOO_LARGE 311 /**< Constant: CONTENT-TOO-LARGE */ -#define AMQP_NO_ROUTE 312 /**< Constant: NO-ROUTE */ -#define AMQP_NO_CONSUMERS 313 /**< Constant: NO-CONSUMERS */ -#define AMQP_ACCESS_REFUSED 403 /**< Constant: ACCESS-REFUSED */ -#define AMQP_NOT_FOUND 404 /**< Constant: NOT-FOUND */ -#define AMQP_RESOURCE_LOCKED 405 /**< Constant: RESOURCE-LOCKED */ -#define AMQP_PRECONDITION_FAILED 406 /**< Constant: PRECONDITION-FAILED */ -#define AMQP_CONNECTION_FORCED 320 /**< Constant: CONNECTION-FORCED */ -#define AMQP_INVALID_PATH 402 /**< Constant: INVALID-PATH */ -#define AMQP_FRAME_ERROR 501 /**< Constant: FRAME-ERROR */ -#define AMQP_SYNTAX_ERROR 502 /**< Constant: SYNTAX-ERROR */ -#define AMQP_COMMAND_INVALID 503 /**< Constant: COMMAND-INVALID */ -#define AMQP_CHANNEL_ERROR 504 /**< Constant: CHANNEL-ERROR */ -#define AMQP_UNEXPECTED_FRAME 505 /**< Constant: UNEXPECTED-FRAME */ -#define AMQP_RESOURCE_ERROR 506 /**< Constant: RESOURCE-ERROR */ -#define AMQP_NOT_ALLOWED 530 /**< Constant: NOT-ALLOWED */ -#define AMQP_NOT_IMPLEMENTED 540 /**< Constant: NOT-IMPLEMENTED */ -#define AMQP_INTERNAL_ERROR 541 /**< Constant: INTERNAL-ERROR */ - -/* Function prototypes. */ - -/** - * Get constant name string from constant - * - * @param [in] constantNumber constant to get the name of - * @returns string describing the constant. String is managed by - * the library and should not be free()'d by the program - */ -AMQP_PUBLIC_FUNCTION -char const *AMQP_CALL amqp_constant_name(int constantNumber); - -/** - * Checks to see if a constant is a hard error - * - * A hard error occurs when something severe enough - * happens that the connection must be closed. - * - * @param [in] constantNumber the error constant - * @returns true if its a hard error, false otherwise - */ -AMQP_PUBLIC_FUNCTION -amqp_boolean_t AMQP_CALL amqp_constant_is_hard_error(int constantNumber); - -/** - * Get method name string from method number - * - * @param [in] methodNumber the method number - * @returns method name string. String is managed by the library - * and should not be freed()'d by the program - */ -AMQP_PUBLIC_FUNCTION -char const *AMQP_CALL amqp_method_name(amqp_method_number_t methodNumber); - -/** - * Check whether a method has content - * - * A method that has content will receive the method frame - * a properties frame, then 1 to N body frames - * - * @param [in] methodNumber the method number - * @returns true if method has content, false otherwise - */ -AMQP_PUBLIC_FUNCTION -amqp_boolean_t AMQP_CALL - amqp_method_has_content(amqp_method_number_t methodNumber); - -/** - * Decodes a method from AMQP wireformat - * - * @param [in] methodNumber the method number for the decoded parameter - * @param [in] pool the memory pool to allocate the decoded method from - * @param [in] encoded the encoded byte string buffer - * @param [out] decoded pointer to the decoded method struct - * @returns 0 on success, an error code otherwise - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_decode_method(amqp_method_number_t methodNumber, - amqp_pool_t *pool, amqp_bytes_t encoded, - void **decoded); - -/** - * Decodes a header frame properties structure from AMQP wireformat - * - * @param [in] class_id the class id for the decoded parameter - * @param [in] pool the memory pool to allocate the decoded properties from - * @param [in] encoded the encoded byte string buffer - * @param [out] decoded pointer to the decoded properties struct - * @returns 0 on success, an error code otherwise - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_decode_properties(uint16_t class_id, amqp_pool_t *pool, - amqp_bytes_t encoded, void **decoded); - -/** - * Encodes a method structure in AMQP wireformat - * - * @param [in] methodNumber the method number for the decoded parameter - * @param [in] decoded the method structure (e.g., amqp_connection_start_t) - * @param [in] encoded an allocated byte buffer for the encoded method - * structure to be written to. If the buffer isn't large enough - * to hold the encoded method, an error code will be returned. - * @returns 0 on success, an error code otherwise. - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_encode_method(amqp_method_number_t methodNumber, - void *decoded, amqp_bytes_t encoded); - -/** - * Encodes a properties structure in AMQP wireformat - * - * @param [in] class_id the class id for the decoded parameter - * @param [in] decoded the properties structure (e.g., amqp_basic_properties_t) - * @param [in] encoded an allocated byte buffer for the encoded properties to - * written to. - * If the buffer isn't large enough to hold the encoded method, an - * an error code will be returned - * @returns 0 on success, an error code otherwise. - */ -AMQP_PUBLIC_FUNCTION -int AMQP_CALL amqp_encode_properties(uint16_t class_id, void *decoded, - amqp_bytes_t encoded); - -/* Method field records. */ - -#define AMQP_CONNECTION_START_METHOD \ - ((amqp_method_number_t)0x000A000A) /**< connection.start method id \ - @internal 10, 10; 655370 */ -/** connection.start method fields */ -typedef struct amqp_connection_start_t_ { - uint8_t version_major; /**< version-major */ - uint8_t version_minor; /**< version-minor */ - amqp_table_t server_properties; /**< server-properties */ - amqp_bytes_t mechanisms; /**< mechanisms */ - amqp_bytes_t locales; /**< locales */ -} amqp_connection_start_t; - -#define AMQP_CONNECTION_START_OK_METHOD \ - ((amqp_method_number_t)0x000A000B) /**< connection.start-ok method id \ - @internal 10, 11; 655371 */ -/** connection.start-ok method fields */ -typedef struct amqp_connection_start_ok_t_ { - amqp_table_t client_properties; /**< client-properties */ - amqp_bytes_t mechanism; /**< mechanism */ - amqp_bytes_t response; /**< response */ - amqp_bytes_t locale; /**< locale */ -} amqp_connection_start_ok_t; - -#define AMQP_CONNECTION_SECURE_METHOD \ - ((amqp_method_number_t)0x000A0014) /**< connection.secure method id \ - @internal 10, 20; 655380 */ -/** connection.secure method fields */ -typedef struct amqp_connection_secure_t_ { - amqp_bytes_t challenge; /**< challenge */ -} amqp_connection_secure_t; - -#define AMQP_CONNECTION_SECURE_OK_METHOD \ - ((amqp_method_number_t)0x000A0015) /**< connection.secure-ok method id \ - @internal 10, 21; 655381 */ -/** connection.secure-ok method fields */ -typedef struct amqp_connection_secure_ok_t_ { - amqp_bytes_t response; /**< response */ -} amqp_connection_secure_ok_t; - -#define AMQP_CONNECTION_TUNE_METHOD \ - ((amqp_method_number_t)0x000A001E) /**< connection.tune method id \ - @internal 10, 30; 655390 */ -/** connection.tune method fields */ -typedef struct amqp_connection_tune_t_ { - uint16_t channel_max; /**< channel-max */ - uint32_t frame_max; /**< frame-max */ - uint16_t heartbeat; /**< heartbeat */ -} amqp_connection_tune_t; - -#define AMQP_CONNECTION_TUNE_OK_METHOD \ - ((amqp_method_number_t)0x000A001F) /**< connection.tune-ok method id \ - @internal 10, 31; 655391 */ -/** connection.tune-ok method fields */ -typedef struct amqp_connection_tune_ok_t_ { - uint16_t channel_max; /**< channel-max */ - uint32_t frame_max; /**< frame-max */ - uint16_t heartbeat; /**< heartbeat */ -} amqp_connection_tune_ok_t; - -#define AMQP_CONNECTION_OPEN_METHOD \ - ((amqp_method_number_t)0x000A0028) /**< connection.open method id \ - @internal 10, 40; 655400 */ -/** connection.open method fields */ -typedef struct amqp_connection_open_t_ { - amqp_bytes_t virtual_host; /**< virtual-host */ - amqp_bytes_t capabilities; /**< capabilities */ - amqp_boolean_t insist; /**< insist */ -} amqp_connection_open_t; - -#define AMQP_CONNECTION_OPEN_OK_METHOD \ - ((amqp_method_number_t)0x000A0029) /**< connection.open-ok method id \ - @internal 10, 41; 655401 */ -/** connection.open-ok method fields */ -typedef struct amqp_connection_open_ok_t_ { - amqp_bytes_t known_hosts; /**< known-hosts */ -} amqp_connection_open_ok_t; - -#define AMQP_CONNECTION_CLOSE_METHOD \ - ((amqp_method_number_t)0x000A0032) /**< connection.close method id \ - @internal 10, 50; 655410 */ -/** connection.close method fields */ -typedef struct amqp_connection_close_t_ { - uint16_t reply_code; /**< reply-code */ - amqp_bytes_t reply_text; /**< reply-text */ - uint16_t class_id; /**< class-id */ - uint16_t method_id; /**< method-id */ -} amqp_connection_close_t; - -#define AMQP_CONNECTION_CLOSE_OK_METHOD \ - ((amqp_method_number_t)0x000A0033) /**< connection.close-ok method id \ - @internal 10, 51; 655411 */ -/** connection.close-ok method fields */ -typedef struct amqp_connection_close_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_connection_close_ok_t; - -#define AMQP_CONNECTION_BLOCKED_METHOD \ - ((amqp_method_number_t)0x000A003C) /**< connection.blocked method id \ - @internal 10, 60; 655420 */ -/** connection.blocked method fields */ -typedef struct amqp_connection_blocked_t_ { - amqp_bytes_t reason; /**< reason */ -} amqp_connection_blocked_t; - -#define AMQP_CONNECTION_UNBLOCKED_METHOD \ - ((amqp_method_number_t)0x000A003D) /**< connection.unblocked method id \ - @internal 10, 61; 655421 */ -/** connection.unblocked method fields */ -typedef struct amqp_connection_unblocked_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_connection_unblocked_t; - -#define AMQP_CHANNEL_OPEN_METHOD \ - ((amqp_method_number_t)0x0014000A) /**< channel.open method id @internal \ - 20, 10; 1310730 */ -/** channel.open method fields */ -typedef struct amqp_channel_open_t_ { - amqp_bytes_t out_of_band; /**< out-of-band */ -} amqp_channel_open_t; - -#define AMQP_CHANNEL_OPEN_OK_METHOD \ - ((amqp_method_number_t)0x0014000B) /**< channel.open-ok method id \ - @internal 20, 11; 1310731 */ -/** channel.open-ok method fields */ -typedef struct amqp_channel_open_ok_t_ { - amqp_bytes_t channel_id; /**< channel-id */ -} amqp_channel_open_ok_t; - -#define AMQP_CHANNEL_FLOW_METHOD \ - ((amqp_method_number_t)0x00140014) /**< channel.flow method id @internal \ - 20, 20; 1310740 */ -/** channel.flow method fields */ -typedef struct amqp_channel_flow_t_ { - amqp_boolean_t active; /**< active */ -} amqp_channel_flow_t; - -#define AMQP_CHANNEL_FLOW_OK_METHOD \ - ((amqp_method_number_t)0x00140015) /**< channel.flow-ok method id \ - @internal 20, 21; 1310741 */ -/** channel.flow-ok method fields */ -typedef struct amqp_channel_flow_ok_t_ { - amqp_boolean_t active; /**< active */ -} amqp_channel_flow_ok_t; - -#define AMQP_CHANNEL_CLOSE_METHOD \ - ((amqp_method_number_t)0x00140028) /**< channel.close method id @internal \ - 20, 40; 1310760 */ -/** channel.close method fields */ -typedef struct amqp_channel_close_t_ { - uint16_t reply_code; /**< reply-code */ - amqp_bytes_t reply_text; /**< reply-text */ - uint16_t class_id; /**< class-id */ - uint16_t method_id; /**< method-id */ -} amqp_channel_close_t; - -#define AMQP_CHANNEL_CLOSE_OK_METHOD \ - ((amqp_method_number_t)0x00140029) /**< channel.close-ok method id \ - @internal 20, 41; 1310761 */ -/** channel.close-ok method fields */ -typedef struct amqp_channel_close_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_channel_close_ok_t; - -#define AMQP_ACCESS_REQUEST_METHOD \ - ((amqp_method_number_t)0x001E000A) /**< access.request method id @internal \ - 30, 10; 1966090 */ -/** access.request method fields */ -typedef struct amqp_access_request_t_ { - amqp_bytes_t realm; /**< realm */ - amqp_boolean_t exclusive; /**< exclusive */ - amqp_boolean_t passive; /**< passive */ - amqp_boolean_t active; /**< active */ - amqp_boolean_t write; /**< write */ - amqp_boolean_t read; /**< read */ -} amqp_access_request_t; - -#define AMQP_ACCESS_REQUEST_OK_METHOD \ - ((amqp_method_number_t)0x001E000B) /**< access.request-ok method id \ - @internal 30, 11; 1966091 */ -/** access.request-ok method fields */ -typedef struct amqp_access_request_ok_t_ { - uint16_t ticket; /**< ticket */ -} amqp_access_request_ok_t; - -#define AMQP_EXCHANGE_DECLARE_METHOD \ - ((amqp_method_number_t)0x0028000A) /**< exchange.declare method id \ - @internal 40, 10; 2621450 */ -/** exchange.declare method fields */ -typedef struct amqp_exchange_declare_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t type; /**< type */ - amqp_boolean_t passive; /**< passive */ - amqp_boolean_t durable; /**< durable */ - amqp_boolean_t auto_delete; /**< auto-delete */ - amqp_boolean_t internal; /**< internal */ - amqp_boolean_t nowait; /**< nowait */ - amqp_table_t arguments; /**< arguments */ -} amqp_exchange_declare_t; - -#define AMQP_EXCHANGE_DECLARE_OK_METHOD \ - ((amqp_method_number_t)0x0028000B) /**< exchange.declare-ok method id \ - @internal 40, 11; 2621451 */ -/** exchange.declare-ok method fields */ -typedef struct amqp_exchange_declare_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_exchange_declare_ok_t; - -#define AMQP_EXCHANGE_DELETE_METHOD \ - ((amqp_method_number_t)0x00280014) /**< exchange.delete method id \ - @internal 40, 20; 2621460 */ -/** exchange.delete method fields */ -typedef struct amqp_exchange_delete_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t exchange; /**< exchange */ - amqp_boolean_t if_unused; /**< if-unused */ - amqp_boolean_t nowait; /**< nowait */ -} amqp_exchange_delete_t; - -#define AMQP_EXCHANGE_DELETE_OK_METHOD \ - ((amqp_method_number_t)0x00280015) /**< exchange.delete-ok method id \ - @internal 40, 21; 2621461 */ -/** exchange.delete-ok method fields */ -typedef struct amqp_exchange_delete_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_exchange_delete_ok_t; - -#define AMQP_EXCHANGE_BIND_METHOD \ - ((amqp_method_number_t)0x0028001E) /**< exchange.bind method id @internal \ - 40, 30; 2621470 */ -/** exchange.bind method fields */ -typedef struct amqp_exchange_bind_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t destination; /**< destination */ - amqp_bytes_t source; /**< source */ - amqp_bytes_t routing_key; /**< routing-key */ - amqp_boolean_t nowait; /**< nowait */ - amqp_table_t arguments; /**< arguments */ -} amqp_exchange_bind_t; - -#define AMQP_EXCHANGE_BIND_OK_METHOD \ - ((amqp_method_number_t)0x0028001F) /**< exchange.bind-ok method id \ - @internal 40, 31; 2621471 */ -/** exchange.bind-ok method fields */ -typedef struct amqp_exchange_bind_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_exchange_bind_ok_t; - -#define AMQP_EXCHANGE_UNBIND_METHOD \ - ((amqp_method_number_t)0x00280028) /**< exchange.unbind method id \ - @internal 40, 40; 2621480 */ -/** exchange.unbind method fields */ -typedef struct amqp_exchange_unbind_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t destination; /**< destination */ - amqp_bytes_t source; /**< source */ - amqp_bytes_t routing_key; /**< routing-key */ - amqp_boolean_t nowait; /**< nowait */ - amqp_table_t arguments; /**< arguments */ -} amqp_exchange_unbind_t; - -#define AMQP_EXCHANGE_UNBIND_OK_METHOD \ - ((amqp_method_number_t)0x00280033) /**< exchange.unbind-ok method id \ - @internal 40, 51; 2621491 */ -/** exchange.unbind-ok method fields */ -typedef struct amqp_exchange_unbind_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_exchange_unbind_ok_t; - -#define AMQP_QUEUE_DECLARE_METHOD \ - ((amqp_method_number_t)0x0032000A) /**< queue.declare method id @internal \ - 50, 10; 3276810 */ -/** queue.declare method fields */ -typedef struct amqp_queue_declare_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_boolean_t passive; /**< passive */ - amqp_boolean_t durable; /**< durable */ - amqp_boolean_t exclusive; /**< exclusive */ - amqp_boolean_t auto_delete; /**< auto-delete */ - amqp_boolean_t nowait; /**< nowait */ - amqp_table_t arguments; /**< arguments */ -} amqp_queue_declare_t; - -#define AMQP_QUEUE_DECLARE_OK_METHOD \ - ((amqp_method_number_t)0x0032000B) /**< queue.declare-ok method id \ - @internal 50, 11; 3276811 */ -/** queue.declare-ok method fields */ -typedef struct amqp_queue_declare_ok_t_ { - amqp_bytes_t queue; /**< queue */ - uint32_t message_count; /**< message-count */ - uint32_t consumer_count; /**< consumer-count */ -} amqp_queue_declare_ok_t; - -#define AMQP_QUEUE_BIND_METHOD \ - ((amqp_method_number_t)0x00320014) /**< queue.bind method id @internal 50, \ - 20; 3276820 */ -/** queue.bind method fields */ -typedef struct amqp_queue_bind_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t routing_key; /**< routing-key */ - amqp_boolean_t nowait; /**< nowait */ - amqp_table_t arguments; /**< arguments */ -} amqp_queue_bind_t; - -#define AMQP_QUEUE_BIND_OK_METHOD \ - ((amqp_method_number_t)0x00320015) /**< queue.bind-ok method id @internal \ - 50, 21; 3276821 */ -/** queue.bind-ok method fields */ -typedef struct amqp_queue_bind_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_queue_bind_ok_t; - -#define AMQP_QUEUE_PURGE_METHOD \ - ((amqp_method_number_t)0x0032001E) /**< queue.purge method id @internal \ - 50, 30; 3276830 */ -/** queue.purge method fields */ -typedef struct amqp_queue_purge_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_boolean_t nowait; /**< nowait */ -} amqp_queue_purge_t; - -#define AMQP_QUEUE_PURGE_OK_METHOD \ - ((amqp_method_number_t)0x0032001F) /**< queue.purge-ok method id @internal \ - 50, 31; 3276831 */ -/** queue.purge-ok method fields */ -typedef struct amqp_queue_purge_ok_t_ { - uint32_t message_count; /**< message-count */ -} amqp_queue_purge_ok_t; - -#define AMQP_QUEUE_DELETE_METHOD \ - ((amqp_method_number_t)0x00320028) /**< queue.delete method id @internal \ - 50, 40; 3276840 */ -/** queue.delete method fields */ -typedef struct amqp_queue_delete_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_boolean_t if_unused; /**< if-unused */ - amqp_boolean_t if_empty; /**< if-empty */ - amqp_boolean_t nowait; /**< nowait */ -} amqp_queue_delete_t; - -#define AMQP_QUEUE_DELETE_OK_METHOD \ - ((amqp_method_number_t)0x00320029) /**< queue.delete-ok method id \ - @internal 50, 41; 3276841 */ -/** queue.delete-ok method fields */ -typedef struct amqp_queue_delete_ok_t_ { - uint32_t message_count; /**< message-count */ -} amqp_queue_delete_ok_t; - -#define AMQP_QUEUE_UNBIND_METHOD \ - ((amqp_method_number_t)0x00320032) /**< queue.unbind method id @internal \ - 50, 50; 3276850 */ -/** queue.unbind method fields */ -typedef struct amqp_queue_unbind_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t routing_key; /**< routing-key */ - amqp_table_t arguments; /**< arguments */ -} amqp_queue_unbind_t; - -#define AMQP_QUEUE_UNBIND_OK_METHOD \ - ((amqp_method_number_t)0x00320033) /**< queue.unbind-ok method id \ - @internal 50, 51; 3276851 */ -/** queue.unbind-ok method fields */ -typedef struct amqp_queue_unbind_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_queue_unbind_ok_t; - -#define AMQP_BASIC_QOS_METHOD \ - ((amqp_method_number_t)0x003C000A) /**< basic.qos method id @internal 60, \ - 10; 3932170 */ -/** basic.qos method fields */ -typedef struct amqp_basic_qos_t_ { - uint32_t prefetch_size; /**< prefetch-size */ - uint16_t prefetch_count; /**< prefetch-count */ - amqp_boolean_t global; /**< global */ -} amqp_basic_qos_t; - -#define AMQP_BASIC_QOS_OK_METHOD \ - ((amqp_method_number_t)0x003C000B) /**< basic.qos-ok method id @internal \ - 60, 11; 3932171 */ -/** basic.qos-ok method fields */ -typedef struct amqp_basic_qos_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_basic_qos_ok_t; - -#define AMQP_BASIC_CONSUME_METHOD \ - ((amqp_method_number_t)0x003C0014) /**< basic.consume method id @internal \ - 60, 20; 3932180 */ -/** basic.consume method fields */ -typedef struct amqp_basic_consume_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_bytes_t consumer_tag; /**< consumer-tag */ - amqp_boolean_t no_local; /**< no-local */ - amqp_boolean_t no_ack; /**< no-ack */ - amqp_boolean_t exclusive; /**< exclusive */ - amqp_boolean_t nowait; /**< nowait */ - amqp_table_t arguments; /**< arguments */ -} amqp_basic_consume_t; - -#define AMQP_BASIC_CONSUME_OK_METHOD \ - ((amqp_method_number_t)0x003C0015) /**< basic.consume-ok method id \ - @internal 60, 21; 3932181 */ -/** basic.consume-ok method fields */ -typedef struct amqp_basic_consume_ok_t_ { - amqp_bytes_t consumer_tag; /**< consumer-tag */ -} amqp_basic_consume_ok_t; - -#define AMQP_BASIC_CANCEL_METHOD \ - ((amqp_method_number_t)0x003C001E) /**< basic.cancel method id @internal \ - 60, 30; 3932190 */ -/** basic.cancel method fields */ -typedef struct amqp_basic_cancel_t_ { - amqp_bytes_t consumer_tag; /**< consumer-tag */ - amqp_boolean_t nowait; /**< nowait */ -} amqp_basic_cancel_t; - -#define AMQP_BASIC_CANCEL_OK_METHOD \ - ((amqp_method_number_t)0x003C001F) /**< basic.cancel-ok method id \ - @internal 60, 31; 3932191 */ -/** basic.cancel-ok method fields */ -typedef struct amqp_basic_cancel_ok_t_ { - amqp_bytes_t consumer_tag; /**< consumer-tag */ -} amqp_basic_cancel_ok_t; - -#define AMQP_BASIC_PUBLISH_METHOD \ - ((amqp_method_number_t)0x003C0028) /**< basic.publish method id @internal \ - 60, 40; 3932200 */ -/** basic.publish method fields */ -typedef struct amqp_basic_publish_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t routing_key; /**< routing-key */ - amqp_boolean_t mandatory; /**< mandatory */ - amqp_boolean_t immediate; /**< immediate */ -} amqp_basic_publish_t; - -#define AMQP_BASIC_RETURN_METHOD \ - ((amqp_method_number_t)0x003C0032) /**< basic.return method id @internal \ - 60, 50; 3932210 */ -/** basic.return method fields */ -typedef struct amqp_basic_return_t_ { - uint16_t reply_code; /**< reply-code */ - amqp_bytes_t reply_text; /**< reply-text */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t routing_key; /**< routing-key */ -} amqp_basic_return_t; - -#define AMQP_BASIC_DELIVER_METHOD \ - ((amqp_method_number_t)0x003C003C) /**< basic.deliver method id @internal \ - 60, 60; 3932220 */ -/** basic.deliver method fields */ -typedef struct amqp_basic_deliver_t_ { - amqp_bytes_t consumer_tag; /**< consumer-tag */ - uint64_t delivery_tag; /**< delivery-tag */ - amqp_boolean_t redelivered; /**< redelivered */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t routing_key; /**< routing-key */ -} amqp_basic_deliver_t; - -#define AMQP_BASIC_GET_METHOD \ - ((amqp_method_number_t)0x003C0046) /**< basic.get method id @internal 60, \ - 70; 3932230 */ -/** basic.get method fields */ -typedef struct amqp_basic_get_t_ { - uint16_t ticket; /**< ticket */ - amqp_bytes_t queue; /**< queue */ - amqp_boolean_t no_ack; /**< no-ack */ -} amqp_basic_get_t; - -#define AMQP_BASIC_GET_OK_METHOD \ - ((amqp_method_number_t)0x003C0047) /**< basic.get-ok method id @internal \ - 60, 71; 3932231 */ -/** basic.get-ok method fields */ -typedef struct amqp_basic_get_ok_t_ { - uint64_t delivery_tag; /**< delivery-tag */ - amqp_boolean_t redelivered; /**< redelivered */ - amqp_bytes_t exchange; /**< exchange */ - amqp_bytes_t routing_key; /**< routing-key */ - uint32_t message_count; /**< message-count */ -} amqp_basic_get_ok_t; - -#define AMQP_BASIC_GET_EMPTY_METHOD \ - ((amqp_method_number_t)0x003C0048) /**< basic.get-empty method id \ - @internal 60, 72; 3932232 */ -/** basic.get-empty method fields */ -typedef struct amqp_basic_get_empty_t_ { - amqp_bytes_t cluster_id; /**< cluster-id */ -} amqp_basic_get_empty_t; - -#define AMQP_BASIC_ACK_METHOD \ - ((amqp_method_number_t)0x003C0050) /**< basic.ack method id @internal 60, \ - 80; 3932240 */ -/** basic.ack method fields */ -typedef struct amqp_basic_ack_t_ { - uint64_t delivery_tag; /**< delivery-tag */ - amqp_boolean_t multiple; /**< multiple */ -} amqp_basic_ack_t; - -#define AMQP_BASIC_REJECT_METHOD \ - ((amqp_method_number_t)0x003C005A) /**< basic.reject method id @internal \ - 60, 90; 3932250 */ -/** basic.reject method fields */ -typedef struct amqp_basic_reject_t_ { - uint64_t delivery_tag; /**< delivery-tag */ - amqp_boolean_t requeue; /**< requeue */ -} amqp_basic_reject_t; - -#define AMQP_BASIC_RECOVER_ASYNC_METHOD \ - ((amqp_method_number_t)0x003C0064) /**< basic.recover-async method id \ - @internal 60, 100; 3932260 */ -/** basic.recover-async method fields */ -typedef struct amqp_basic_recover_async_t_ { - amqp_boolean_t requeue; /**< requeue */ -} amqp_basic_recover_async_t; - -#define AMQP_BASIC_RECOVER_METHOD \ - ((amqp_method_number_t)0x003C006E) /**< basic.recover method id @internal \ - 60, 110; 3932270 */ -/** basic.recover method fields */ -typedef struct amqp_basic_recover_t_ { - amqp_boolean_t requeue; /**< requeue */ -} amqp_basic_recover_t; - -#define AMQP_BASIC_RECOVER_OK_METHOD \ - ((amqp_method_number_t)0x003C006F) /**< basic.recover-ok method id \ - @internal 60, 111; 3932271 */ -/** basic.recover-ok method fields */ -typedef struct amqp_basic_recover_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_basic_recover_ok_t; - -#define AMQP_BASIC_NACK_METHOD \ - ((amqp_method_number_t)0x003C0078) /**< basic.nack method id @internal 60, \ - 120; 3932280 */ -/** basic.nack method fields */ -typedef struct amqp_basic_nack_t_ { - uint64_t delivery_tag; /**< delivery-tag */ - amqp_boolean_t multiple; /**< multiple */ - amqp_boolean_t requeue; /**< requeue */ -} amqp_basic_nack_t; - -#define AMQP_TX_SELECT_METHOD \ - ((amqp_method_number_t)0x005A000A) /**< tx.select method id @internal 90, \ - 10; 5898250 */ -/** tx.select method fields */ -typedef struct amqp_tx_select_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_select_t; - -#define AMQP_TX_SELECT_OK_METHOD \ - ((amqp_method_number_t)0x005A000B) /**< tx.select-ok method id @internal \ - 90, 11; 5898251 */ -/** tx.select-ok method fields */ -typedef struct amqp_tx_select_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_select_ok_t; - -#define AMQP_TX_COMMIT_METHOD \ - ((amqp_method_number_t)0x005A0014) /**< tx.commit method id @internal 90, \ - 20; 5898260 */ -/** tx.commit method fields */ -typedef struct amqp_tx_commit_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_commit_t; - -#define AMQP_TX_COMMIT_OK_METHOD \ - ((amqp_method_number_t)0x005A0015) /**< tx.commit-ok method id @internal \ - 90, 21; 5898261 */ -/** tx.commit-ok method fields */ -typedef struct amqp_tx_commit_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_commit_ok_t; - -#define AMQP_TX_ROLLBACK_METHOD \ - ((amqp_method_number_t)0x005A001E) /**< tx.rollback method id @internal \ - 90, 30; 5898270 */ -/** tx.rollback method fields */ -typedef struct amqp_tx_rollback_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_rollback_t; - -#define AMQP_TX_ROLLBACK_OK_METHOD \ - ((amqp_method_number_t)0x005A001F) /**< tx.rollback-ok method id @internal \ - 90, 31; 5898271 */ -/** tx.rollback-ok method fields */ -typedef struct amqp_tx_rollback_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_rollback_ok_t; - -#define AMQP_CONFIRM_SELECT_METHOD \ - ((amqp_method_number_t)0x0055000A) /**< confirm.select method id @internal \ - 85, 10; 5570570 */ -/** confirm.select method fields */ -typedef struct amqp_confirm_select_t_ { - amqp_boolean_t nowait; /**< nowait */ -} amqp_confirm_select_t; - -#define AMQP_CONFIRM_SELECT_OK_METHOD \ - ((amqp_method_number_t)0x0055000B) /**< confirm.select-ok method id \ - @internal 85, 11; 5570571 */ -/** confirm.select-ok method fields */ -typedef struct amqp_confirm_select_ok_t_ { - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_confirm_select_ok_t; - -/* Class property records. */ -#define AMQP_CONNECTION_CLASS \ - (0x000A) /**< connection class id @internal 10 \ - */ -/** connection class properties */ -typedef struct amqp_connection_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_connection_properties_t; - -#define AMQP_CHANNEL_CLASS (0x0014) /**< channel class id @internal 20 */ -/** channel class properties */ -typedef struct amqp_channel_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_channel_properties_t; - -#define AMQP_ACCESS_CLASS (0x001E) /**< access class id @internal 30 */ -/** access class properties */ -typedef struct amqp_access_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_access_properties_t; - -#define AMQP_EXCHANGE_CLASS (0x0028) /**< exchange class id @internal 40 */ -/** exchange class properties */ -typedef struct amqp_exchange_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_exchange_properties_t; - -#define AMQP_QUEUE_CLASS (0x0032) /**< queue class id @internal 50 */ -/** queue class properties */ -typedef struct amqp_queue_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_queue_properties_t; - -#define AMQP_BASIC_CLASS (0x003C) /**< basic class id @internal 60 */ -#define AMQP_BASIC_CONTENT_TYPE_FLAG (1 << 15) -#define AMQP_BASIC_CONTENT_ENCODING_FLAG (1 << 14) -#define AMQP_BASIC_HEADERS_FLAG (1 << 13) -#define AMQP_BASIC_DELIVERY_MODE_FLAG (1 << 12) -#define AMQP_BASIC_PRIORITY_FLAG (1 << 11) -#define AMQP_BASIC_CORRELATION_ID_FLAG (1 << 10) -#define AMQP_BASIC_REPLY_TO_FLAG (1 << 9) -#define AMQP_BASIC_EXPIRATION_FLAG (1 << 8) -#define AMQP_BASIC_MESSAGE_ID_FLAG (1 << 7) -#define AMQP_BASIC_TIMESTAMP_FLAG (1 << 6) -#define AMQP_BASIC_TYPE_FLAG (1 << 5) -#define AMQP_BASIC_USER_ID_FLAG (1 << 4) -#define AMQP_BASIC_APP_ID_FLAG (1 << 3) -#define AMQP_BASIC_CLUSTER_ID_FLAG (1 << 2) -/** basic class properties */ -typedef struct amqp_basic_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - amqp_bytes_t content_type; /**< content-type */ - amqp_bytes_t content_encoding; /**< content-encoding */ - amqp_table_t headers; /**< headers */ - uint8_t delivery_mode; /**< delivery-mode */ - uint8_t priority; /**< priority */ - amqp_bytes_t correlation_id; /**< correlation-id */ - amqp_bytes_t reply_to; /**< reply-to */ - amqp_bytes_t expiration; /**< expiration */ - amqp_bytes_t message_id; /**< message-id */ - uint64_t timestamp; /**< timestamp */ - amqp_bytes_t type; /**< type */ - amqp_bytes_t user_id; /**< user-id */ - amqp_bytes_t app_id; /**< app-id */ - amqp_bytes_t cluster_id; /**< cluster-id */ -} amqp_basic_properties_t; - -#define AMQP_TX_CLASS (0x005A) /**< tx class id @internal 90 */ -/** tx class properties */ -typedef struct amqp_tx_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_tx_properties_t; - -#define AMQP_CONFIRM_CLASS (0x0055) /**< confirm class id @internal 85 */ -/** confirm class properties */ -typedef struct amqp_confirm_properties_t_ { - amqp_flags_t _flags; /**< bit-mask of set fields */ - char dummy; /**< Dummy field to avoid empty struct */ -} amqp_confirm_properties_t; - -/* API functions for methods */ - -/** - * amqp_channel_open - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @returns amqp_channel_open_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_channel_open_ok_t *AMQP_CALL - amqp_channel_open(amqp_connection_state_t state, amqp_channel_t channel); -/** - * amqp_channel_flow - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] active active - * @returns amqp_channel_flow_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_channel_flow_ok_t *AMQP_CALL - amqp_channel_flow(amqp_connection_state_t state, amqp_channel_t channel, - amqp_boolean_t active); -/** - * amqp_exchange_declare - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] exchange exchange - * @param [in] type type - * @param [in] passive passive - * @param [in] durable durable - * @param [in] auto_delete auto_delete - * @param [in] internal internal - * @param [in] arguments arguments - * @returns amqp_exchange_declare_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_exchange_declare_ok_t *AMQP_CALL amqp_exchange_declare( - amqp_connection_state_t state, amqp_channel_t channel, - amqp_bytes_t exchange, amqp_bytes_t type, amqp_boolean_t passive, - amqp_boolean_t durable, amqp_boolean_t auto_delete, amqp_boolean_t internal, - amqp_table_t arguments); -/** - * amqp_exchange_delete - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] exchange exchange - * @param [in] if_unused if_unused - * @returns amqp_exchange_delete_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_exchange_delete_ok_t *AMQP_CALL - amqp_exchange_delete(amqp_connection_state_t state, amqp_channel_t channel, - amqp_bytes_t exchange, amqp_boolean_t if_unused); -/** - * amqp_exchange_bind - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] destination destination - * @param [in] source source - * @param [in] routing_key routing_key - * @param [in] arguments arguments - * @returns amqp_exchange_bind_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_exchange_bind_ok_t *AMQP_CALL - amqp_exchange_bind(amqp_connection_state_t state, amqp_channel_t channel, - amqp_bytes_t destination, amqp_bytes_t source, - amqp_bytes_t routing_key, amqp_table_t arguments); -/** - * amqp_exchange_unbind - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] destination destination - * @param [in] source source - * @param [in] routing_key routing_key - * @param [in] arguments arguments - * @returns amqp_exchange_unbind_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_exchange_unbind_ok_t *AMQP_CALL - amqp_exchange_unbind(amqp_connection_state_t state, amqp_channel_t channel, - amqp_bytes_t destination, amqp_bytes_t source, - amqp_bytes_t routing_key, amqp_table_t arguments); -/** - * amqp_queue_declare - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] queue queue - * @param [in] passive passive - * @param [in] durable durable - * @param [in] exclusive exclusive - * @param [in] auto_delete auto_delete - * @param [in] arguments arguments - * @returns amqp_queue_declare_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_queue_declare_ok_t *AMQP_CALL amqp_queue_declare( - amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, - amqp_boolean_t passive, amqp_boolean_t durable, amqp_boolean_t exclusive, - amqp_boolean_t auto_delete, amqp_table_t arguments); -/** - * amqp_queue_bind - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] queue queue - * @param [in] exchange exchange - * @param [in] routing_key routing_key - * @param [in] arguments arguments - * @returns amqp_queue_bind_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_queue_bind_ok_t *AMQP_CALL amqp_queue_bind( - amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, - amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_table_t arguments); -/** - * amqp_queue_purge - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] queue queue - * @returns amqp_queue_purge_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_queue_purge_ok_t *AMQP_CALL amqp_queue_purge(amqp_connection_state_t state, - amqp_channel_t channel, - amqp_bytes_t queue); -/** - * amqp_queue_delete - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] queue queue - * @param [in] if_unused if_unused - * @param [in] if_empty if_empty - * @returns amqp_queue_delete_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_queue_delete_ok_t *AMQP_CALL amqp_queue_delete( - amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, - amqp_boolean_t if_unused, amqp_boolean_t if_empty); -/** - * amqp_queue_unbind - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] queue queue - * @param [in] exchange exchange - * @param [in] routing_key routing_key - * @param [in] arguments arguments - * @returns amqp_queue_unbind_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_queue_unbind_ok_t *AMQP_CALL amqp_queue_unbind( - amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, - amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_table_t arguments); -/** - * amqp_basic_qos - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] prefetch_size prefetch_size - * @param [in] prefetch_count prefetch_count - * @param [in] global global - * @returns amqp_basic_qos_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_basic_qos_ok_t *AMQP_CALL amqp_basic_qos(amqp_connection_state_t state, - amqp_channel_t channel, - uint32_t prefetch_size, - uint16_t prefetch_count, - amqp_boolean_t global); -/** - * amqp_basic_consume - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] queue queue - * @param [in] consumer_tag consumer_tag - * @param [in] no_local no_local - * @param [in] no_ack no_ack - * @param [in] exclusive exclusive - * @param [in] arguments arguments - * @returns amqp_basic_consume_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_basic_consume_ok_t *AMQP_CALL amqp_basic_consume( - amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, - amqp_bytes_t consumer_tag, amqp_boolean_t no_local, amqp_boolean_t no_ack, - amqp_boolean_t exclusive, amqp_table_t arguments); -/** - * amqp_basic_cancel - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] consumer_tag consumer_tag - * @returns amqp_basic_cancel_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_basic_cancel_ok_t *AMQP_CALL - amqp_basic_cancel(amqp_connection_state_t state, amqp_channel_t channel, - amqp_bytes_t consumer_tag); -/** - * amqp_basic_recover - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @param [in] requeue requeue - * @returns amqp_basic_recover_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_basic_recover_ok_t *AMQP_CALL - amqp_basic_recover(amqp_connection_state_t state, amqp_channel_t channel, - amqp_boolean_t requeue); -/** - * amqp_tx_select - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @returns amqp_tx_select_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_tx_select_ok_t *AMQP_CALL amqp_tx_select(amqp_connection_state_t state, - amqp_channel_t channel); -/** - * amqp_tx_commit - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @returns amqp_tx_commit_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_tx_commit_ok_t *AMQP_CALL amqp_tx_commit(amqp_connection_state_t state, - amqp_channel_t channel); -/** - * amqp_tx_rollback - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @returns amqp_tx_rollback_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_tx_rollback_ok_t *AMQP_CALL amqp_tx_rollback(amqp_connection_state_t state, - amqp_channel_t channel); -/** - * amqp_confirm_select - * - * @param [in] state connection state - * @param [in] channel the channel to do the RPC on - * @returns amqp_confirm_select_ok_t - */ -AMQP_PUBLIC_FUNCTION -amqp_confirm_select_ok_t *AMQP_CALL - amqp_confirm_select(amqp_connection_state_t state, amqp_channel_t channel); - -AMQP_END_DECLS - -#endif /* AMQP_FRAMING_H */ diff --git a/ext/librabbitmq/macos/include/amqp_tcp_socket.h b/ext/librabbitmq/macos/include/amqp_tcp_socket.h deleted file mode 100644 index 3e9d82f54..000000000 --- a/ext/librabbitmq/macos/include/amqp_tcp_socket.h +++ /dev/null @@ -1,68 +0,0 @@ -/** \file */ -/* - * Portions created by Alan Antonuk are Copyright (c) 2013-2014 Alan Antonuk. - * All Rights Reserved. - * - * Portions created by Michael Steinert are Copyright (c) 2012-2013 Michael - * Steinert. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -/** - * A TCP socket connection. - */ - -#ifndef AMQP_TCP_SOCKET_H -#define AMQP_TCP_SOCKET_H - -#include - -AMQP_BEGIN_DECLS - -/** - * Create a new TCP socket. - * - * Call amqp_connection_close() to release socket resources. - * - * \return A new socket object or NULL if an error occurred. - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -amqp_socket_t *AMQP_CALL amqp_tcp_socket_new(amqp_connection_state_t state); - -/** - * Assign an open file descriptor to a socket object. - * - * This function must not be used in conjunction with amqp_socket_open(), i.e. - * the socket connection should already be open(2) when this function is - * called. - * - * \param [in,out] self A TCP socket object. - * \param [in] sockfd An open socket descriptor. - * - * \since v0.4.0 - */ -AMQP_PUBLIC_FUNCTION -void AMQP_CALL amqp_tcp_socket_set_sockfd(amqp_socket_t *self, int sockfd); - -AMQP_END_DECLS - -#endif /* AMQP_TCP_SOCKET_H */ diff --git a/ext/librabbitmq/macos/lib/librabbitmq.a b/ext/librabbitmq/macos/lib/librabbitmq.a deleted file mode 100644 index f3d27b31dcda47dde6601e85aa5901ff1b3a66d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95704 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 |g!EiVi5*iarI=?_M9XGjw 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 zg#`j{2`*?LDDXsetNetconfMaster((void *)_controller); // Join existing networks in networks.d @@ -991,15 +986,7 @@ public: if (cdbp.length() > 0) _controllerDbPath = cdbp; - json &rmq = settings["rabbitmq"]; - if (rmq.is_object() && _mqc == NULL) { - fprintf(stderr, "Reading RabbitMQ Config\n"); - _mqc = new MQConfig; - _mqc->port = rmq["port"]; - _mqc->host = OSUtils::jsonString(rmq["host"], ""); - _mqc->username = OSUtils::jsonString(rmq["username"], ""); - _mqc->password = OSUtils::jsonString(rmq["password"], ""); - } + // TODO: Redis config // Bind to wildcard instead of to specific interfaces (disables full tunnel capability) json &bind = settings["bind"]; From 154470b570cb39f18e518f1a76cdc744894a1313 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 11 May 2020 15:03:56 -0700 Subject: [PATCH 048/362] add original hiredis --- controller/EmbeddedNetworkController.cpp | 7 +- controller/EmbeddedNetworkController.hpp | 5 +- controller/PostgreSQL.cpp | 13 +- controller/PostgreSQL.hpp | 6 +- ext/hiredis-0.14.1/.gitignore | 7 + ext/hiredis-0.14.1/.travis.yml | 45 + ext/hiredis-0.14.1/CHANGELOG.md | 190 +++ ext/hiredis-0.14.1/COPYING | 29 + ext/hiredis-0.14.1/Makefile | 214 +++ ext/hiredis-0.14.1/README.md | 410 ++++++ ext/hiredis-0.14.1/adapters/ae.h | 127 ++ ext/hiredis-0.14.1/adapters/glib.h | 153 ++ ext/hiredis-0.14.1/adapters/ivykis.h | 81 ++ ext/hiredis-0.14.1/adapters/libev.h | 147 ++ ext/hiredis-0.14.1/adapters/libevent.h | 108 ++ ext/hiredis-0.14.1/adapters/libuv.h | 122 ++ ext/hiredis-0.14.1/adapters/macosx.h | 114 ++ ext/hiredis-0.14.1/adapters/qt.h | 135 ++ ext/hiredis-0.14.1/alloc.c | 65 + ext/hiredis-0.14.1/alloc.h | 53 + ext/hiredis-0.14.1/appveyor.yml | 23 + ext/hiredis-0.14.1/async.c | 717 ++++++++++ ext/hiredis-0.14.1/async.h | 130 ++ ext/hiredis-0.14.1/dict.c | 339 +++++ ext/hiredis-0.14.1/dict.h | 126 ++ ext/hiredis-0.14.1/examples/example-ae.c | 62 + ext/hiredis-0.14.1/examples/example-glib.c | 73 + ext/hiredis-0.14.1/examples/example-ivykis.c | 58 + ext/hiredis-0.14.1/examples/example-libev.c | 52 + .../examples/example-libevent.c | 53 + ext/hiredis-0.14.1/examples/example-libuv.c | 53 + ext/hiredis-0.14.1/examples/example-macosx.c | 66 + ext/hiredis-0.14.1/examples/example-qt.cpp | 46 + ext/hiredis-0.14.1/examples/example-qt.h | 32 + ext/hiredis-0.14.1/examples/example.c | 78 + ext/hiredis-0.14.1/fmacros.h | 12 + ext/hiredis-0.14.1/hiredis.c | 1006 +++++++++++++ ext/hiredis-0.14.1/hiredis.h | 200 +++ ext/hiredis-0.14.1/net.c | 477 +++++++ ext/hiredis-0.14.1/net.h | 49 + ext/hiredis-0.14.1/read.c | 598 ++++++++ ext/hiredis-0.14.1/read.h | 111 ++ ext/hiredis-0.14.1/sds.c | 1272 +++++++++++++++++ ext/hiredis-0.14.1/sds.h | 273 ++++ ext/hiredis-0.14.1/sdsalloc.h | 42 + ext/hiredis-0.14.1/test.c | 923 ++++++++++++ ext/hiredis-0.14.1/win32.h | 42 + make-mac.mk | 2 +- objects.mk | 1 + service/OneService.cpp | 18 +- 50 files changed, 8952 insertions(+), 13 deletions(-) create mode 100644 ext/hiredis-0.14.1/.gitignore create mode 100644 ext/hiredis-0.14.1/.travis.yml create mode 100644 ext/hiredis-0.14.1/CHANGELOG.md create mode 100644 ext/hiredis-0.14.1/COPYING create mode 100644 ext/hiredis-0.14.1/Makefile create mode 100644 ext/hiredis-0.14.1/README.md create mode 100644 ext/hiredis-0.14.1/adapters/ae.h create mode 100644 ext/hiredis-0.14.1/adapters/glib.h create mode 100644 ext/hiredis-0.14.1/adapters/ivykis.h create mode 100644 ext/hiredis-0.14.1/adapters/libev.h create mode 100644 ext/hiredis-0.14.1/adapters/libevent.h create mode 100644 ext/hiredis-0.14.1/adapters/libuv.h create mode 100644 ext/hiredis-0.14.1/adapters/macosx.h create mode 100644 ext/hiredis-0.14.1/adapters/qt.h create mode 100644 ext/hiredis-0.14.1/alloc.c create mode 100644 ext/hiredis-0.14.1/alloc.h create mode 100644 ext/hiredis-0.14.1/appveyor.yml create mode 100644 ext/hiredis-0.14.1/async.c create mode 100644 ext/hiredis-0.14.1/async.h create mode 100644 ext/hiredis-0.14.1/dict.c create mode 100644 ext/hiredis-0.14.1/dict.h create mode 100644 ext/hiredis-0.14.1/examples/example-ae.c create mode 100644 ext/hiredis-0.14.1/examples/example-glib.c create mode 100644 ext/hiredis-0.14.1/examples/example-ivykis.c create mode 100644 ext/hiredis-0.14.1/examples/example-libev.c create mode 100644 ext/hiredis-0.14.1/examples/example-libevent.c create mode 100644 ext/hiredis-0.14.1/examples/example-libuv.c create mode 100644 ext/hiredis-0.14.1/examples/example-macosx.c create mode 100644 ext/hiredis-0.14.1/examples/example-qt.cpp create mode 100644 ext/hiredis-0.14.1/examples/example-qt.h create mode 100644 ext/hiredis-0.14.1/examples/example.c create mode 100644 ext/hiredis-0.14.1/fmacros.h create mode 100644 ext/hiredis-0.14.1/hiredis.c create mode 100644 ext/hiredis-0.14.1/hiredis.h create mode 100644 ext/hiredis-0.14.1/net.c create mode 100644 ext/hiredis-0.14.1/net.h create mode 100644 ext/hiredis-0.14.1/read.c create mode 100644 ext/hiredis-0.14.1/read.h create mode 100644 ext/hiredis-0.14.1/sds.c create mode 100644 ext/hiredis-0.14.1/sds.h create mode 100644 ext/hiredis-0.14.1/sdsalloc.h create mode 100644 ext/hiredis-0.14.1/test.c create mode 100644 ext/hiredis-0.14.1/win32.h diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index ef2ce2cfb..b2bd7bfb9 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -456,14 +456,15 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule) } // anonymous namespace -EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort) : +EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort, RedisConfig *rc) : _startTime(OSUtils::now()), _listenPort(listenPort), _node(node), _ztPath(ztPath), _path(dbPath), _sender((NetworkController::Sender *)0), - _db(this) + _db(this), + _rc(rc) { } @@ -484,7 +485,7 @@ void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender) #ifdef ZT_CONTROLLER_USE_LIBPQ if ((_path.length() > 9)&&(_path.substr(0,9) == "postgres:")) { - _db.addDB(std::shared_ptr(new PostgreSQL(_signingId,_path.substr(9).c_str(), _listenPort))); + _db.addDB(std::shared_ptr(new PostgreSQL(_signingId,_path.substr(9).c_str(), _listenPort, _rc))); } else { #endif _db.addDB(std::shared_ptr(new FileDB(_path.c_str()))); diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index d09e08ac6..3fb421074 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -43,6 +43,7 @@ namespace ZeroTier { class Node; +struct RedisConfig; class EmbeddedNetworkController : public NetworkController,public DB::ChangeListener { @@ -51,7 +52,7 @@ public: * @param node Parent node * @param dbPath Database path (file path or database credentials) */ - EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort); + EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort, RedisConfig *rc); virtual ~EmbeddedNetworkController(); virtual void init(const Identity &signingId,Sender *sender); @@ -148,6 +149,8 @@ private: std::unordered_map< _MemberStatusKey,_MemberStatus,_MemberStatusHash > _memberStatus; std::mutex _memberStatus_l; + + RedisConfig *_rc; }; } // namespace ZeroTier diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 72acdca9e..91cbdb789 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -18,7 +18,7 @@ #include "../node/Constants.hpp" #include "EmbeddedNetworkController.hpp" #include "../version.h" -#include "hiredis.h" +#include "Redis.hpp" #include #include @@ -68,7 +68,7 @@ std::string join(const std::vector &elements, const char * const se using namespace ZeroTier; -PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort) +PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, RedisConfig *rc) : DB() , _myId(myId) , _myAddress(myId.address()) @@ -77,6 +77,7 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort) , _run(1) , _waitNoticePrinted(false) , _listenPort(listenPort) + , _rc(rc) { char myAddress[64]; _myAddressStr = myId.address().toString(myAddress); @@ -717,10 +718,10 @@ void PostgreSQL::networksDbWatcher() initializeNetworks(conn); - if (false) { - // PQfinish(conn); - // conn = NULL; - // _networksWatcher_RabbitMQ(); + if (_rc) { + PQfinish(conn); + conn = NULL; + _networksWatcher_Redis(); } else { _networksWatcher_Postgres(conn); PQfinish(conn); diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index dcad35e7d..5d14e2ff6 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -26,6 +26,8 @@ typedef struct pg_conn PGconn; namespace ZeroTier { +struct RedisConfig; + /** * A controller database driver that talks to PostgreSQL * @@ -35,7 +37,7 @@ namespace ZeroTier { class PostgreSQL : public DB { public: - PostgreSQL(const Identity &myId, const char *path, int listenPort); + PostgreSQL(const Identity &myId, const char *path, int listenPort, RedisConfig *rc); virtual ~PostgreSQL(); virtual bool waitForReady(); @@ -94,6 +96,8 @@ private: mutable volatile bool _waitNoticePrinted; int _listenPort; + + RedisConfig *_rc; }; } // namespace ZeroTier diff --git a/ext/hiredis-0.14.1/.gitignore b/ext/hiredis-0.14.1/.gitignore new file mode 100644 index 000000000..c44b5c537 --- /dev/null +++ b/ext/hiredis-0.14.1/.gitignore @@ -0,0 +1,7 @@ +/hiredis-test +/examples/hiredis-example* +/*.o +/*.so +/*.dylib +/*.a +/*.pc diff --git a/ext/hiredis-0.14.1/.travis.yml b/ext/hiredis-0.14.1/.travis.yml new file mode 100644 index 000000000..faf2ce684 --- /dev/null +++ b/ext/hiredis-0.14.1/.travis.yml @@ -0,0 +1,45 @@ +language: c +sudo: false +compiler: + - gcc + - clang + +os: + - linux + - osx + +branches: + only: + - staging + - trying + - master + +before_script: + - if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi + +addons: + apt: + packages: + - libc6-dbg + - libc6-dev + - libc6:i386 + - libc6-dev-i386 + - libc6-dbg:i386 + - gcc-multilib + - valgrind + +env: + - CFLAGS="-Werror" + - PRE="valgrind --track-origins=yes --leak-check=full" + - TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror" + - TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" + +matrix: + exclude: + - os: osx + env: PRE="valgrind --track-origins=yes --leak-check=full" + + - os: osx + env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" + +script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example diff --git a/ext/hiredis-0.14.1/CHANGELOG.md b/ext/hiredis-0.14.1/CHANGELOG.md new file mode 100644 index 000000000..f8e577369 --- /dev/null +++ b/ext/hiredis-0.14.1/CHANGELOG.md @@ -0,0 +1,190 @@ +**NOTE: BREAKING CHANGES upgrading from 0.13.x to 0.14.x **: + +* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now + protocol errors. This is consistent with the RESP specification. On 32-bit + platforms, the upper bound is lowered to `SIZE_MAX`. + +* Change `redisReply.len` to `size_t`, as it denotes the the size of a string + + User code should compare this to `size_t` values as well. If it was used to + compare to other values, casting might be necessary or can be removed, if + casting was applied before. + +### 0.14.1 (2020-03-13) + +* Adds safe allocation wrappers (CVE-2020-7105, #747, #752) (Michael Grunder) + +### 0.14.0 (2018-09-25) + +* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b]) +* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537]) +* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622]) +* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8]) +* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8]) +* Fix bulk and multi-bulk length truncation (Justin Brewer [109197]) +* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94]) +* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6]) +* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1]) +* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b]) +* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96]) +* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234]) +* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129]) +* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c]) +* Fix libevent leak (zfz [515228]) +* Clean up GCC warning (Ichito Nagata [2ec774]) +* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88]) +* Solaris compilation fix (Donald Whyte [41b07d]) +* Reorder linker arguments when building examples (Tustfarm-heart [06eedd]) +* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999]) +* libuv use after free fix (Paul Scott [cbb956]) +* Properly close socket fd on reconnect attempt (WSL [64d1ec]) +* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78]) +* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5]) +* Update libevent (Chris Xin [386802]) +* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e]) +* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6]) +* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3]) +* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb]) +* Compatibility fix for strerror_r (Tom Lee [bb1747]) +* Properly detect integer parse/overflow errors (Justin Brewer [93421f]) +* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40]) +* Catch a buffer overflow when formatting the error message +* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 +* Fix warnings, when compiled with -Wshadow +* Make hiredis compile in Cygwin on Windows, now CI-tested + +**BREAKING CHANGES**: + +* Remove backwards compatibility macro's + +This removes the following old function aliases, use the new name now: + +| Old | New | +| --------------------------- | ---------------------- | +| redisReplyReaderCreate | redisReaderCreate | +| redisReplyReaderCreate | redisReaderCreate | +| redisReplyReaderFree | redisReaderFree | +| redisReplyReaderFeed | redisReaderFeed | +| redisReplyReaderGetReply | redisReaderGetReply | +| redisReplyReaderSetPrivdata | redisReaderSetPrivdata | +| redisReplyReaderGetObject | redisReaderGetObject | +| redisReplyReaderGetError | redisReaderGetError | + +* The `DEBUG` variable in the Makefile was renamed to `DEBUG_FLAGS` + +Previously it broke some builds for people that had `DEBUG` set to some arbitrary value, +due to debugging other software. +By renaming we avoid unintentional name clashes. + +Simply rename `DEBUG` to `DEBUG_FLAGS` in your environment to make it working again. + +### 0.13.3 (2015-09-16) + +* Revert "Clear `REDIS_CONNECTED` flag when connection is closed". +* Make tests pass on FreeBSD (Thanks, Giacomo Olgeni) + + +If the `REDIS_CONNECTED` flag is cleared, +the async onDisconnect callback function will never be called. +This causes problems as the disconnect is never reported back to the user. + +### 0.13.2 (2015-08-25) + +* Prevent crash on pending replies in async code (Thanks, @switch-st) +* Clear `REDIS_CONNECTED` flag when connection is closed (Thanks, Jerry Jacobs) +* Add MacOS X addapter (Thanks, @dizzus) +* Add Qt adapter (Thanks, Pietro Cerutti) +* Add Ivykis adapter (Thanks, Gergely Nagy) + +All adapters are provided as is and are only tested where possible. + +### 0.13.1 (2015-05-03) + +This is a bug fix release. +The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code. +Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects. +Other non-C99 code can now use hiredis as usual again. +Sorry for the inconvenience. + +* Fix memory leak in async reply handling (Salvatore Sanfilippo) +* Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa) + +### 0.13.0 (2015-04-16) + +This release adds a minimal Windows compatibility layer. +The parser, standalone since v0.12.0, can now be compiled on Windows +(and thus used in other client libraries as well) + +* Windows compatibility layer for parser code (tzickel) +* Properly escape data printed to PKGCONF file (Dan Skorupski) +* Fix tests when assert() undefined (Keith Bennett, Matt Stancliff) +* Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra) + +### 0.12.1 (2015-01-26) + +* Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location +* Fix `make test` as 32 bit build on 64 bit platform + +### 0.12.0 (2015-01-22) + +* Add optional KeepAlive support + +* Try again on EINTR errors + +* Add libuv adapter + +* Add IPv6 support + +* Remove possibility of multiple close on same fd + +* Add ability to bind source address on connect + +* Add redisConnectFd() and redisFreeKeepFd() + +* Fix getaddrinfo() memory leak + +* Free string if it is unused (fixes memory leak) + +* Improve redisAppendCommandArgv performance 2.5x + +* Add support for SO_REUSEADDR + +* Fix redisvFormatCommand format parsing + +* Add GLib 2.0 adapter + +* Refactor reading code into read.c + +* Fix errno error buffers to not clobber errors + +* Generate pkgconf during build + +* Silence _BSD_SOURCE warnings + +* Improve digit counting for multibulk creation + + +### 0.11.0 + +* Increase the maximum multi-bulk reply depth to 7. + +* Increase the read buffer size from 2k to 16k. + +* Use poll(2) instead of select(2) to support large fds (>= 1024). + +### 0.10.1 + +* Makefile overhaul. Important to check out if you override one or more + variables using environment variables or via arguments to the "make" tool. + +* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements + being created by the default reply object functions. + +* Issue #43: Don't crash in an asynchronous context when Redis returns an error + reply after the connection has been made (this happens when the maximum + number of connections is reached). + +### 0.10.0 + +* See commit log. + diff --git a/ext/hiredis-0.14.1/COPYING b/ext/hiredis-0.14.1/COPYING new file mode 100644 index 000000000..a5fc97395 --- /dev/null +++ b/ext/hiredis-0.14.1/COPYING @@ -0,0 +1,29 @@ +Copyright (c) 2009-2011, Salvatore Sanfilippo +Copyright (c) 2010-2011, Pieter Noordhuis + +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. diff --git a/ext/hiredis-0.14.1/Makefile b/ext/hiredis-0.14.1/Makefile new file mode 100644 index 000000000..d1f005af4 --- /dev/null +++ b/ext/hiredis-0.14.1/Makefile @@ -0,0 +1,214 @@ +# Hiredis Makefile +# Copyright (C) 2010-2011 Salvatore Sanfilippo +# Copyright (C) 2010-2011 Pieter Noordhuis +# This file is released under the BSD license, see the COPYING file + +OBJ=net.o hiredis.o sds.o async.o read.o alloc.o +EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib +TESTS=hiredis-test +LIBNAME=libhiredis +PKGCONFNAME=hiredis.pc + +HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') +HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}') +HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}') +HIREDIS_SONAME=$(shell grep HIREDIS_SONAME hiredis.h | awk '{print $$3}') + +# Installation related variables and target +PREFIX?=/usr/local +INCLUDE_PATH?=include/hiredis +LIBRARY_PATH?=lib +PKGCONF_PATH?=pkgconfig +INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH) +INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH) +INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH) + +# redis-server configuration used for testing +REDIS_PORT=56379 +REDIS_SERVER=redis-server +define REDIS_TEST_CONFIG + daemonize yes + pidfile /tmp/hiredis-test-redis.pid + port $(REDIS_PORT) + bind 127.0.0.1 + unixsocket /tmp/hiredis-test-redis.sock +endef +export REDIS_TEST_CONFIG + +# Fallback to gcc when $CC is not in $PATH. +CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc') +CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++') +OPTIMIZATION?=-O3 +WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings +DEBUG_FLAGS?= -g -ggdb +REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) +REAL_LDFLAGS=$(LDFLAGS) + +DYLIBSUFFIX=so +STLIBSUFFIX=a +DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) +DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) +DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) +DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) +STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) +STLIB_MAKE_CMD=ar rcs $(STLIBNAME) + +# Platform-specific overrides +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') +ifeq ($(uname_S),SunOS) + REAL_LDFLAGS+= -ldl -lnsl -lsocket + DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) +endif +ifeq ($(uname_S),Darwin) + DYLIBSUFFIX=dylib + DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX) + DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) +endif + +all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) + +# Deps (use make dep to generate this) +alloc.o: alloc.c fmacros.h alloc.h +async.o: async.c fmacros.h alloc.h async.h hiredis.h read.h sds.h net.h dict.c dict.h +dict.o: dict.c fmacros.h alloc.h dict.h +hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h alloc.h net.h +net.o: net.c fmacros.h net.h hiredis.h read.h sds.h alloc.h +read.o: read.c fmacros.h read.h sds.h +sds.o: sds.c sds.h sdsalloc.h +test.o: test.c fmacros.h hiredis.h read.h sds.h alloc.h net.h + +$(DYLIBNAME): $(OBJ) + $(DYLIB_MAKE_CMD) $(OBJ) + +$(STLIBNAME): $(OBJ) + $(STLIB_MAKE_CMD) $(OBJ) + +dynamic: $(DYLIBNAME) +static: $(STLIBNAME) + +# Binaries: +hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) + +hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) + +hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) + +hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME) + +hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) + +ifndef AE_DIR +hiredis-example-ae: + @echo "Please specify AE_DIR (e.g. /src)" + @false +else +hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME) +endif + +ifndef LIBUV_DIR +hiredis-example-libuv: + @echo "Please specify LIBUV_DIR (e.g. ../libuv/)" + @false +else +hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) +endif + +ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),) +hiredis-example-qt: + @echo "Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR" + @false +else +hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME) + $(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ + $(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore + $(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ + $(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore + $(CXX) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore -L$(QT_LIBRARY_DIR) qt-adapter-moc.o qt-example-moc.o $< -pthread $(STLIBNAME) -lQtCore +endif + +hiredis-example: examples/example.c $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) + +examples: $(EXAMPLES) + +hiredis-test: test.o $(STLIBNAME) + +hiredis-%: %.o $(STLIBNAME) + $(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) + +test: hiredis-test + ./hiredis-test + +check: hiredis-test + @echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) - + $(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \ + ( kill `cat /tmp/hiredis-test-redis.pid` && false ) + kill `cat /tmp/hiredis-test-redis.pid` + +.c.o: + $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< + +clean: + rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov + +dep: + $(CC) -MM *.c + +INSTALL?= cp -pPR + +$(PKGCONFNAME): hiredis.h + @echo "Generating $@ for pkgconfig..." + @echo prefix=$(PREFIX) > $@ + @echo exec_prefix=\$${prefix} >> $@ + @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@ + @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@ + @echo >> $@ + @echo Name: hiredis >> $@ + @echo Description: Minimalistic C client library for Redis. >> $@ + @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ + @echo Libs: -L\$${libdir} -lhiredis >> $@ + @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ + +install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) + mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) + $(INSTALL) hiredis.h async.h read.h sds.h alloc.h $(INSTALL_INCLUDE_PATH) + $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters + $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) + cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) + $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) + mkdir -p $(INSTALL_PKGCONF_PATH) + $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH) + +32bit: + @echo "" + @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386" + @echo "" + $(MAKE) CFLAGS="-m32" LDFLAGS="-m32" + +32bit-vars: + $(eval CFLAGS=-m32) + $(eval LDFLAGS=-m32) + +gprof: + $(MAKE) CFLAGS="-pg" LDFLAGS="-pg" + +gcov: + $(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" + +coverage: gcov + make check + mkdir -p tmp/lcov + lcov -d . -c -o tmp/lcov/hiredis.info + genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info + +noopt: + $(MAKE) OPTIMIZATION="" + +.PHONY: all test check clean dep install 32bit 32bit-vars gprof gcov noopt diff --git a/ext/hiredis-0.14.1/README.md b/ext/hiredis-0.14.1/README.md new file mode 100644 index 000000000..50e2e6be6 --- /dev/null +++ b/ext/hiredis-0.14.1/README.md @@ -0,0 +1,410 @@ +[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) + +**This Readme reflects the latest changed in the master branch. See [v0.14.1](https://github.com/redis/hiredis/tree/v0.14.1) for the Readme and documentation for the latest release.** + +# HIREDIS + +Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. + +It is minimalistic because it just adds minimal support for the protocol, but +at the same time it uses a high level printf-alike API in order to make it +much higher level than otherwise suggested by its minimal code base and the +lack of explicit bindings for every Redis command. + +Apart from supporting sending commands and receiving replies, it comes with +a reply parser that is decoupled from the I/O layer. It +is a stream parser designed for easy reusability, which can for instance be used +in higher level language bindings for efficient reply parsing. + +Hiredis only supports the binary-safe Redis protocol, so you can use it with any +Redis version >= 1.2.0. + +The library comes with multiple APIs. There is the +*synchronous API*, the *asynchronous API* and the *reply parsing API*. + +## IMPORTANT: Breaking changes when upgrading from 0.13.x -> 0.14.x + +Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now +protocol errors. This is consistent with the RESP specification. On 32-bit +platforms, the upper bound is lowered to `SIZE_MAX`. + +Change `redisReply.len` to `size_t`, as it denotes the the size of a string + +User code should compare this to `size_t` values as well. If it was used to +compare to other values, casting might be necessary or can be removed, if +casting was applied before. + +For a detailed list of changes please view our [Changelog](CHANGELOG.md). + +## Synchronous API + +To consume the synchronous API, there are only a few function calls that need to be introduced: + +```c +redisContext *redisConnect(const char *ip, int port); +void *redisCommand(redisContext *c, const char *format, ...); +void freeReplyObject(void *reply); +``` + +### Connecting + +The function `redisConnect` is used to create a so-called `redisContext`. The +context is where Hiredis holds state for a connection. The `redisContext` +struct has an integer `err` field that is non-zero when the connection is in +an error state. The field `errstr` will contain a string with a description of +the error. More information on errors can be found in the **Errors** section. +After trying to connect to Redis using `redisConnect` you should +check the `err` field to see if establishing the connection was successful: +```c +redisContext *c = redisConnect("127.0.0.1", 6379); +if (c == NULL || c->err) { + if (c) { + printf("Error: %s\n", c->errstr); + // handle error + } else { + printf("Can't allocate redis context\n"); + } +} +``` + +*Note: A `redisContext` is not thread-safe.* + +### Sending commands + +There are several ways to issue commands to Redis. The first that will be introduced is +`redisCommand`. This function takes a format similar to printf. In the simplest form, +it is used like this: +```c +reply = redisCommand(context, "SET foo bar"); +``` + +The specifier `%s` interpolates a string in the command, and uses `strlen` to +determine the length of the string: +```c +reply = redisCommand(context, "SET foo %s", value); +``` +When you need to pass binary safe strings in a command, the `%b` specifier can be +used. Together with a pointer to the string, it requires a `size_t` length argument +of the string: +```c +reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen); +``` +Internally, Hiredis splits the command in different arguments and will +convert it to the protocol used to communicate with Redis. +One or more spaces separates arguments, so you can use the specifiers +anywhere in an argument: +```c +reply = redisCommand(context, "SET key:%s %s", myid, value); +``` + +### Using replies + +The return value of `redisCommand` holds a reply when the command was +successfully executed. When an error occurs, the return value is `NULL` and +the `err` field in the context will be set (see section on **Errors**). +Once an error is returned the context cannot be reused and you should set up +a new connection. + +The standard replies that `redisCommand` are of the type `redisReply`. The +`type` field in the `redisReply` should be used to test what kind of reply +was received: + +* **`REDIS_REPLY_STATUS`**: + * The command replied with a status reply. The status string can be accessed using `reply->str`. + The length of this string can be accessed using `reply->len`. + +* **`REDIS_REPLY_ERROR`**: + * The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`. + +* **`REDIS_REPLY_INTEGER`**: + * The command replied with an integer. The integer value can be accessed using the + `reply->integer` field of type `long long`. + +* **`REDIS_REPLY_NIL`**: + * The command replied with a **nil** object. There is no data to access. + +* **`REDIS_REPLY_STRING`**: + * A bulk (string) reply. The value of the reply can be accessed using `reply->str`. + The length of this string can be accessed using `reply->len`. + +* **`REDIS_REPLY_ARRAY`**: + * A multi bulk reply. The number of elements in the multi bulk reply is stored in + `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well + and can be accessed via `reply->element[..index..]`. + Redis may reply with nested arrays but this is fully supported. + +Replies should be freed using the `freeReplyObject()` function. +Note that this function will take care of freeing sub-reply objects +contained in arrays and nested arrays, so there is no need for the user to +free the sub replies (it is actually harmful and will corrupt the memory). + +**Important:** the current version of hiredis (0.10.0) frees replies when the +asynchronous API is used. This means you should not call `freeReplyObject` when +you use this API. The reply is cleaned up by hiredis _after_ the callback +returns. This behavior will probably change in future releases, so make sure to +keep an eye on the changelog when upgrading (see issue #39). + +### Cleaning up + +To disconnect and free the context the following function can be used: +```c +void redisFree(redisContext *c); +``` +This function immediately closes the socket and then frees the allocations done in +creating the context. + +### Sending commands (cont'd) + +Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands. +It has the following prototype: +```c +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); +``` +It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the +arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will +use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments +need to be binary safe, the entire array of lengths `argvlen` should be provided. + +The return value has the same semantic as `redisCommand`. + +### Pipelining + +To explain how Hiredis supports pipelining in a blocking connection, there needs to be +understanding of the internal execution flow. + +When any of the functions in the `redisCommand` family is called, Hiredis first formats the +command according to the Redis protocol. The formatted command is then put in the output buffer +of the context. This output buffer is dynamic, so it can hold any number of commands. +After the command is put in the output buffer, `redisGetReply` is called. This function has the +following two execution paths: + +1. The input buffer is non-empty: + * Try to parse a single reply from the input buffer and return it + * If no reply could be parsed, continue at *2* +2. The input buffer is empty: + * Write the **entire** output buffer to the socket + * Read from the socket until a single reply could be parsed + +The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply +is expected on the socket. To pipeline commands, the only things that needs to be done is +filling up the output buffer. For this cause, two commands can be used that are identical +to the `redisCommand` family, apart from not returning a reply: +```c +void redisAppendCommand(redisContext *c, const char *format, ...); +void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); +``` +After calling either function one or more times, `redisGetReply` can be used to receive the +subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where +the latter means an error occurred while reading a reply. Just as with the other commands, +the `err` field in the context can be used to find out what the cause of this error is. + +The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and +a single call to `read(2)`): +```c +redisReply *reply; +redisAppendCommand(context,"SET foo bar"); +redisAppendCommand(context,"GET foo"); +redisGetReply(context,&reply); // reply for SET +freeReplyObject(reply); +redisGetReply(context,&reply); // reply for GET +freeReplyObject(reply); +``` +This API can also be used to implement a blocking subscriber: +```c +reply = redisCommand(context,"SUBSCRIBE foo"); +freeReplyObject(reply); +while(redisGetReply(context,&reply) == REDIS_OK) { + // consume message + freeReplyObject(reply); +} +``` +### Errors + +When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is +returned. The `err` field inside the context will be non-zero and set to one of the +following constants: + +* **`REDIS_ERR_IO`**: + There was an I/O error while creating the connection, trying to write + to the socket or read from the socket. If you included `errno.h` in your + application, you can use the global `errno` variable to find out what is + wrong. + +* **`REDIS_ERR_EOF`**: + The server closed the connection which resulted in an empty read. + +* **`REDIS_ERR_PROTOCOL`**: + There was an error while parsing the protocol. + +* **`REDIS_ERR_OTHER`**: + Any other error. Currently, it is only used when a specified hostname to connect + to cannot be resolved. + +In every case, the `errstr` field in the context will be set to hold a string representation +of the error. + +## Asynchronous API + +Hiredis comes with an asynchronous API that works easily with any event library. +Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html) +and [libevent](http://monkey.org/~provos/libevent/). + +### Connecting + +The function `redisAsyncConnect` can be used to establish a non-blocking connection to +Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field +should be checked after creation to see if there were errors creating the connection. +Because the connection that will be created is non-blocking, the kernel is not able to +instantly return if the specified host and port is able to accept a connection. + +*Note: A `redisAsyncContext` is not thread-safe.* + +```c +redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); +if (c->err) { + printf("Error: %s\n", c->errstr); + // handle error +} +``` + +The asynchronous context can hold a disconnect callback function that is called when the +connection is disconnected (either because of an error or per user request). This function should +have the following prototype: +```c +void(const redisAsyncContext *c, int status); +``` +On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the +user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err` +field in the context can be accessed to find out the cause of the error. + +The context object is always freed after the disconnect callback fired. When a reconnect is needed, +the disconnect callback is a good point to do so. + +Setting the disconnect callback can only be done once per context. For subsequent calls it will +return `REDIS_ERR`. The function to set the disconnect callback has the following prototype: +```c +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); +``` +### Sending commands and their callbacks + +In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. +Therefore, unlike the synchronous API, there is only a single way to send commands. +Because commands are sent to Redis asynchronously, issuing a command requires a callback function +that is called when the reply is received. Reply callbacks should have the following prototype: +```c +void(redisAsyncContext *c, void *reply, void *privdata); +``` +The `privdata` argument can be used to curry arbitrary data to the callback from the point where +the command is initially queued for execution. + +The functions that can be used to issue commands in an asynchronous context are: +```c +int redisAsyncCommand( + redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, + const char *format, ...); +int redisAsyncCommandArgv( + redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, + int argc, const char **argv, const size_t *argvlen); +``` +Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command +was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection +is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is +returned on calls to the `redisAsyncCommand` family. + +If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback +for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only +valid for the duration of the callback. + +All pending callbacks are called with a `NULL` reply when the context encountered an error. + +### Disconnecting + +An asynchronous connection can be terminated using: +```c +void redisAsyncDisconnect(redisAsyncContext *ac); +``` +When this function is called, the connection is **not** immediately terminated. Instead, new +commands are no longer accepted and the connection is only terminated when all pending commands +have been written to the socket, their respective replies have been read and their respective +callbacks have been executed. After this, the disconnection callback is executed with the +`REDIS_OK` status and the context object is freed. + +### Hooking it up to event library *X* + +There are a few hooks that need to be set on the context object after it is created. +See the `adapters/` directory for bindings to *libev* and *libevent*. + +## Reply parsing API + +Hiredis comes with a reply parsing API that makes it easy for writing higher +level language bindings. + +The reply parsing API consists of the following functions: +```c +redisReader *redisReaderCreate(void); +void redisReaderFree(redisReader *reader); +int redisReaderFeed(redisReader *reader, const char *buf, size_t len); +int redisReaderGetReply(redisReader *reader, void **reply); +``` +The same set of functions are used internally by hiredis when creating a +normal Redis context, the above API just exposes it to the user for a direct +usage. + +### Usage + +The function `redisReaderCreate` creates a `redisReader` structure that holds a +buffer with unparsed data and state for the protocol parser. + +Incoming data -- most likely from a socket -- can be placed in the internal +buffer of the `redisReader` using `redisReaderFeed`. This function will make a +copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed +when `redisReaderGetReply` is called. This function returns an integer status +and a reply object (as described above) via `void **reply`. The returned status +can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went +wrong (either a protocol error, or an out of memory error). + +The parser limits the level of nesting for multi bulk payloads to 7. If the +multi bulk nesting level is higher than this, the parser returns an error. + +### Customizing replies + +The function `redisReaderGetReply` creates `redisReply` and makes the function +argument `reply` point to the created `redisReply` variable. For instance, if +the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply` +will hold the status as a vanilla C string. However, the functions that are +responsible for creating instances of the `redisReply` can be customized by +setting the `fn` field on the `redisReader` struct. This should be done +immediately after creating the `redisReader`. + +For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c) +uses customized reply object functions to create Ruby objects. + +### Reader max buffer + +Both when using the Reader API directly or when using it indirectly via a +normal Redis context, the redisReader structure uses a buffer in order to +accumulate data from the server. +Usually this buffer is destroyed when it is empty and is larger than 16 +KiB in order to avoid wasting memory in unused buffers + +However when working with very big payloads destroying the buffer may slow +down performances considerably, so it is possible to modify the max size of +an idle buffer changing the value of the `maxbuf` field of the reader structure +to the desired value. The special value of 0 means that there is no maximum +value for an idle buffer, so the buffer will never get freed. + +For instance if you have a normal Redis context you can set the maximum idle +buffer to zero (unlimited) just with: +```c +context->reader->maxbuf = 0; +``` +This should be done only in order to maximize performances when working with +large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again +as soon as possible in order to prevent allocation of useless memory. + +## AUTHORS + +Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and +Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. +Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and +Jan-Erik Rediger (janerik at fnordig dot com) diff --git a/ext/hiredis-0.14.1/adapters/ae.h b/ext/hiredis-0.14.1/adapters/ae.h new file mode 100644 index 000000000..03939928d --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/ae.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + +#ifndef __HIREDIS_AE_H__ +#define __HIREDIS_AE_H__ +#include +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisAeEvents { + redisAsyncContext *context; + aeEventLoop *loop; + int fd; + int reading, writing; +} redisAeEvents; + +static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { + ((void)el); ((void)fd); ((void)mask); + + redisAeEvents *e = (redisAeEvents*)privdata; + redisAsyncHandleRead(e->context); +} + +static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { + ((void)el); ((void)fd); ((void)mask); + + redisAeEvents *e = (redisAeEvents*)privdata; + redisAsyncHandleWrite(e->context); +} + +static void redisAeAddRead(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (!e->reading) { + e->reading = 1; + aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e); + } +} + +static void redisAeDelRead(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (e->reading) { + e->reading = 0; + aeDeleteFileEvent(loop,e->fd,AE_READABLE); + } +} + +static void redisAeAddWrite(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (!e->writing) { + e->writing = 1; + aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e); + } +} + +static void redisAeDelWrite(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (e->writing) { + e->writing = 0; + aeDeleteFileEvent(loop,e->fd,AE_WRITABLE); + } +} + +static void redisAeCleanup(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + redisAeDelRead(privdata); + redisAeDelWrite(privdata); + free(e); +} + +static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisAeEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisAeEvents*)hi_malloc(sizeof(*e)); + e->context = ac; + e->loop = loop; + e->fd = c->fd; + e->reading = e->writing = 0; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisAeAddRead; + ac->ev.delRead = redisAeDelRead; + ac->ev.addWrite = redisAeAddWrite; + ac->ev.delWrite = redisAeDelWrite; + ac->ev.cleanup = redisAeCleanup; + ac->ev.data = e; + + return REDIS_OK; +} +#endif diff --git a/ext/hiredis-0.14.1/adapters/glib.h b/ext/hiredis-0.14.1/adapters/glib.h new file mode 100644 index 000000000..e0a6411d3 --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/glib.h @@ -0,0 +1,153 @@ +#ifndef __HIREDIS_GLIB_H__ +#define __HIREDIS_GLIB_H__ + +#include + +#include "../hiredis.h" +#include "../async.h" + +typedef struct +{ + GSource source; + redisAsyncContext *ac; + GPollFD poll_fd; +} RedisSource; + +static void +redis_source_add_read (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + g_return_if_fail(source); + source->poll_fd.events |= G_IO_IN; + g_main_context_wakeup(g_source_get_context((GSource *)data)); +} + +static void +redis_source_del_read (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + g_return_if_fail(source); + source->poll_fd.events &= ~G_IO_IN; + g_main_context_wakeup(g_source_get_context((GSource *)data)); +} + +static void +redis_source_add_write (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + g_return_if_fail(source); + source->poll_fd.events |= G_IO_OUT; + g_main_context_wakeup(g_source_get_context((GSource *)data)); +} + +static void +redis_source_del_write (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + g_return_if_fail(source); + source->poll_fd.events &= ~G_IO_OUT; + g_main_context_wakeup(g_source_get_context((GSource *)data)); +} + +static void +redis_source_cleanup (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + + g_return_if_fail(source); + + redis_source_del_read(source); + redis_source_del_write(source); + /* + * It is not our responsibility to remove ourself from the + * current main loop. However, we will remove the GPollFD. + */ + if (source->poll_fd.fd >= 0) { + g_source_remove_poll((GSource *)data, &source->poll_fd); + source->poll_fd.fd = -1; + } +} + +static gboolean +redis_source_prepare (GSource *source, + gint *timeout_) +{ + RedisSource *redis = (RedisSource *)source; + *timeout_ = -1; + return !!(redis->poll_fd.events & redis->poll_fd.revents); +} + +static gboolean +redis_source_check (GSource *source) +{ + RedisSource *redis = (RedisSource *)source; + return !!(redis->poll_fd.events & redis->poll_fd.revents); +} + +static gboolean +redis_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + RedisSource *redis = (RedisSource *)source; + + if ((redis->poll_fd.revents & G_IO_OUT)) { + redisAsyncHandleWrite(redis->ac); + redis->poll_fd.revents &= ~G_IO_OUT; + } + + if ((redis->poll_fd.revents & G_IO_IN)) { + redisAsyncHandleRead(redis->ac); + redis->poll_fd.revents &= ~G_IO_IN; + } + + if (callback) { + return callback(user_data); + } + + return TRUE; +} + +static void +redis_source_finalize (GSource *source) +{ + RedisSource *redis = (RedisSource *)source; + + if (redis->poll_fd.fd >= 0) { + g_source_remove_poll(source, &redis->poll_fd); + redis->poll_fd.fd = -1; + } +} + +static GSource * +redis_source_new (redisAsyncContext *ac) +{ + static GSourceFuncs source_funcs = { + .prepare = redis_source_prepare, + .check = redis_source_check, + .dispatch = redis_source_dispatch, + .finalize = redis_source_finalize, + }; + redisContext *c = &ac->c; + RedisSource *source; + + g_return_val_if_fail(ac != NULL, NULL); + + source = (RedisSource *)g_source_new(&source_funcs, sizeof *source); + source->ac = ac; + source->poll_fd.fd = c->fd; + source->poll_fd.events = 0; + source->poll_fd.revents = 0; + g_source_add_poll((GSource *)source, &source->poll_fd); + + ac->ev.addRead = redis_source_add_read; + ac->ev.delRead = redis_source_del_read; + ac->ev.addWrite = redis_source_add_write; + ac->ev.delWrite = redis_source_del_write; + ac->ev.cleanup = redis_source_cleanup; + ac->ev.data = source; + + return (GSource *)source; +} + +#endif /* __HIREDIS_GLIB_H__ */ diff --git a/ext/hiredis-0.14.1/adapters/ivykis.h b/ext/hiredis-0.14.1/adapters/ivykis.h new file mode 100644 index 000000000..75616ee24 --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/ivykis.h @@ -0,0 +1,81 @@ +#ifndef __HIREDIS_IVYKIS_H__ +#define __HIREDIS_IVYKIS_H__ +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisIvykisEvents { + redisAsyncContext *context; + struct iv_fd fd; +} redisIvykisEvents; + +static void redisIvykisReadEvent(void *arg) { + redisAsyncContext *context = (redisAsyncContext *)arg; + redisAsyncHandleRead(context); +} + +static void redisIvykisWriteEvent(void *arg) { + redisAsyncContext *context = (redisAsyncContext *)arg; + redisAsyncHandleWrite(context); +} + +static void redisIvykisAddRead(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent); +} + +static void redisIvykisDelRead(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_in(&e->fd, NULL); +} + +static void redisIvykisAddWrite(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent); +} + +static void redisIvykisDelWrite(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_out(&e->fd, NULL); +} + +static void redisIvykisCleanup(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + + iv_fd_unregister(&e->fd); + free(e); +} + +static int redisIvykisAttach(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisIvykisEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisIvykisEvents*)hi_malloc(sizeof(*e)); + e->context = ac; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisIvykisAddRead; + ac->ev.delRead = redisIvykisDelRead; + ac->ev.addWrite = redisIvykisAddWrite; + ac->ev.delWrite = redisIvykisDelWrite; + ac->ev.cleanup = redisIvykisCleanup; + ac->ev.data = e; + + /* Initialize and install read/write events */ + IV_FD_INIT(&e->fd); + e->fd.fd = c->fd; + e->fd.handler_in = redisIvykisReadEvent; + e->fd.handler_out = redisIvykisWriteEvent; + e->fd.handler_err = NULL; + e->fd.cookie = e->context; + + iv_fd_register(&e->fd); + + return REDIS_OK; +} +#endif diff --git a/ext/hiredis-0.14.1/adapters/libev.h b/ext/hiredis-0.14.1/adapters/libev.h new file mode 100644 index 000000000..abad43634 --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/libev.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + +#ifndef __HIREDIS_LIBEV_H__ +#define __HIREDIS_LIBEV_H__ +#include +#include +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisLibevEvents { + redisAsyncContext *context; + struct ev_loop *loop; + int reading, writing; + ev_io rev, wev; +} redisLibevEvents; + +static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { +#if EV_MULTIPLICITY + ((void)loop); +#endif + ((void)revents); + + redisLibevEvents *e = (redisLibevEvents*)watcher->data; + redisAsyncHandleRead(e->context); +} + +static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { +#if EV_MULTIPLICITY + ((void)loop); +#endif + ((void)revents); + + redisLibevEvents *e = (redisLibevEvents*)watcher->data; + redisAsyncHandleWrite(e->context); +} + +static void redisLibevAddRead(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (!e->reading) { + e->reading = 1; + ev_io_start(EV_A_ &e->rev); + } +} + +static void redisLibevDelRead(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (e->reading) { + e->reading = 0; + ev_io_stop(EV_A_ &e->rev); + } +} + +static void redisLibevAddWrite(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (!e->writing) { + e->writing = 1; + ev_io_start(EV_A_ &e->wev); + } +} + +static void redisLibevDelWrite(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (e->writing) { + e->writing = 0; + ev_io_stop(EV_A_ &e->wev); + } +} + +static void redisLibevCleanup(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + redisLibevDelRead(privdata); + redisLibevDelWrite(privdata); + free(e); +} + +static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisLibevEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisLibevEvents*)hi_malloc(sizeof(*e)); + e->context = ac; +#if EV_MULTIPLICITY + e->loop = loop; +#else + e->loop = NULL; +#endif + e->reading = e->writing = 0; + e->rev.data = e; + e->wev.data = e; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisLibevAddRead; + ac->ev.delRead = redisLibevDelRead; + ac->ev.addWrite = redisLibevAddWrite; + ac->ev.delWrite = redisLibevDelWrite; + ac->ev.cleanup = redisLibevCleanup; + ac->ev.data = e; + + /* Initialize read/write events */ + ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ); + ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE); + return REDIS_OK; +} + +#endif diff --git a/ext/hiredis-0.14.1/adapters/libevent.h b/ext/hiredis-0.14.1/adapters/libevent.h new file mode 100644 index 000000000..f2330d6f0 --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/libevent.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + +#ifndef __HIREDIS_LIBEVENT_H__ +#define __HIREDIS_LIBEVENT_H__ +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisLibeventEvents { + redisAsyncContext *context; + struct event *rev, *wev; +} redisLibeventEvents; + +static void redisLibeventReadEvent(int fd, short event, void *arg) { + ((void)fd); ((void)event); + redisLibeventEvents *e = (redisLibeventEvents*)arg; + redisAsyncHandleRead(e->context); +} + +static void redisLibeventWriteEvent(int fd, short event, void *arg) { + ((void)fd); ((void)event); + redisLibeventEvents *e = (redisLibeventEvents*)arg; + redisAsyncHandleWrite(e->context); +} + +static void redisLibeventAddRead(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_add(e->rev,NULL); +} + +static void redisLibeventDelRead(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_del(e->rev); +} + +static void redisLibeventAddWrite(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_add(e->wev,NULL); +} + +static void redisLibeventDelWrite(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_del(e->wev); +} + +static void redisLibeventCleanup(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_free(e->rev); + event_free(e->wev); + free(e); +} + +static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { + redisContext *c = &(ac->c); + redisLibeventEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e)); + e->context = ac; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisLibeventAddRead; + ac->ev.delRead = redisLibeventDelRead; + ac->ev.addWrite = redisLibeventAddWrite; + ac->ev.delWrite = redisLibeventDelWrite; + ac->ev.cleanup = redisLibeventCleanup; + ac->ev.data = e; + + /* Initialize and install read/write events */ + e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e); + e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e); + event_add(e->rev, NULL); + event_add(e->wev, NULL); + return REDIS_OK; +} +#endif diff --git a/ext/hiredis-0.14.1/adapters/libuv.h b/ext/hiredis-0.14.1/adapters/libuv.h new file mode 100644 index 000000000..ff08c25e1 --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/libuv.h @@ -0,0 +1,122 @@ +#ifndef __HIREDIS_LIBUV_H__ +#define __HIREDIS_LIBUV_H__ +#include +#include +#include "../hiredis.h" +#include "../async.h" +#include + +typedef struct redisLibuvEvents { + redisAsyncContext* context; + uv_poll_t handle; + int events; +} redisLibuvEvents; + + +static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + + if (status != 0) { + return; + } + + if (p->context != NULL && (events & UV_READABLE)) { + redisAsyncHandleRead(p->context); + } + if (p->context != NULL && (events & UV_WRITABLE)) { + redisAsyncHandleWrite(p->context); + } +} + + +static void redisLibuvAddRead(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events |= UV_READABLE; + + uv_poll_start(&p->handle, p->events, redisLibuvPoll); +} + + +static void redisLibuvDelRead(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events &= ~UV_READABLE; + + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } +} + + +static void redisLibuvAddWrite(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events |= UV_WRITABLE; + + uv_poll_start(&p->handle, p->events, redisLibuvPoll); +} + + +static void redisLibuvDelWrite(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events &= ~UV_WRITABLE; + + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } +} + + +static void on_close(uv_handle_t* handle) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + + free(p); +} + + +static void redisLibuvCleanup(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->context = NULL; // indicate that context might no longer exist + uv_close((uv_handle_t*)&p->handle, on_close); +} + + +static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { + redisContext *c = &(ac->c); + + if (ac->ev.data != NULL) { + return REDIS_ERR; + } + + ac->ev.addRead = redisLibuvAddRead; + ac->ev.delRead = redisLibuvDelRead; + ac->ev.addWrite = redisLibuvAddWrite; + ac->ev.delWrite = redisLibuvDelWrite; + ac->ev.cleanup = redisLibuvCleanup; + + redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p)); + + if (!p) { + return REDIS_ERR; + } + + memset(p, 0, sizeof(*p)); + + if (uv_poll_init(loop, &p->handle, c->fd) != 0) { + return REDIS_ERR; + } + + ac->ev.data = p; + p->handle.data = p; + p->context = ac; + + return REDIS_OK; +} +#endif diff --git a/ext/hiredis-0.14.1/adapters/macosx.h b/ext/hiredis-0.14.1/adapters/macosx.h new file mode 100644 index 000000000..72121f606 --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/macosx.h @@ -0,0 +1,114 @@ +// +// Created by Дмитрий Бахвалов on 13.07.15. +// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. +// + +#ifndef __HIREDIS_MACOSX_H__ +#define __HIREDIS_MACOSX_H__ + +#include + +#include "../hiredis.h" +#include "../async.h" + +typedef struct { + redisAsyncContext *context; + CFSocketRef socketRef; + CFRunLoopSourceRef sourceRef; +} RedisRunLoop; + +static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) { + if( redisRunLoop != NULL ) { + if( redisRunLoop->sourceRef != NULL ) { + CFRunLoopSourceInvalidate(redisRunLoop->sourceRef); + CFRelease(redisRunLoop->sourceRef); + } + if( redisRunLoop->socketRef != NULL ) { + CFSocketInvalidate(redisRunLoop->socketRef); + CFRelease(redisRunLoop->socketRef); + } + free(redisRunLoop); + } + return REDIS_ERR; +} + +static void redisMacOSAddRead(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); +} + +static void redisMacOSDelRead(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); +} + +static void redisMacOSAddWrite(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); +} + +static void redisMacOSDelWrite(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); +} + +static void redisMacOSCleanup(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + freeRedisRunLoop(redisRunLoop); +} + +static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) { + redisAsyncContext* context = (redisAsyncContext*) info; + + switch (callbackType) { + case kCFSocketReadCallBack: + redisAsyncHandleRead(context); + break; + + case kCFSocketWriteCallBack: + redisAsyncHandleWrite(context); + break; + + default: + break; + } +} + +static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) { + redisContext *redisCtx = &(redisAsyncCtx->c); + + /* Nothing should be attached when something is already attached */ + if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR; + + RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop)); + if( !redisRunLoop ) return REDIS_ERR; + + /* Setup redis stuff */ + redisRunLoop->context = redisAsyncCtx; + + redisAsyncCtx->ev.addRead = redisMacOSAddRead; + redisAsyncCtx->ev.delRead = redisMacOSDelRead; + redisAsyncCtx->ev.addWrite = redisMacOSAddWrite; + redisAsyncCtx->ev.delWrite = redisMacOSDelWrite; + redisAsyncCtx->ev.cleanup = redisMacOSCleanup; + redisAsyncCtx->ev.data = redisRunLoop; + + /* Initialize and install read/write events */ + CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL }; + + redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd, + kCFSocketReadCallBack | kCFSocketWriteCallBack, + redisMacOSAsyncCallback, + &socketCtx); + if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop); + + redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0); + if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop); + + CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode); + + return REDIS_OK; +} + +#endif + diff --git a/ext/hiredis-0.14.1/adapters/qt.h b/ext/hiredis-0.14.1/adapters/qt.h new file mode 100644 index 000000000..5cc02e6ce --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/qt.h @@ -0,0 +1,135 @@ +/*- + * Copyright (C) 2014 Pietro Cerutti + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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. + */ + +#ifndef __HIREDIS_QT_H__ +#define __HIREDIS_QT_H__ +#include +#include "../async.h" + +static void RedisQtAddRead(void *); +static void RedisQtDelRead(void *); +static void RedisQtAddWrite(void *); +static void RedisQtDelWrite(void *); +static void RedisQtCleanup(void *); + +class RedisQtAdapter : public QObject { + + Q_OBJECT + + friend + void RedisQtAddRead(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->addRead(); + } + + friend + void RedisQtDelRead(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->delRead(); + } + + friend + void RedisQtAddWrite(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->addWrite(); + } + + friend + void RedisQtDelWrite(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->delWrite(); + } + + friend + void RedisQtCleanup(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->cleanup(); + } + + public: + RedisQtAdapter(QObject * parent = 0) + : QObject(parent), m_ctx(0), m_read(0), m_write(0) { } + + ~RedisQtAdapter() { + if (m_ctx != 0) { + m_ctx->ev.data = NULL; + } + } + + int setContext(redisAsyncContext * ac) { + if (ac->ev.data != NULL) { + return REDIS_ERR; + } + m_ctx = ac; + m_ctx->ev.data = this; + m_ctx->ev.addRead = RedisQtAddRead; + m_ctx->ev.delRead = RedisQtDelRead; + m_ctx->ev.addWrite = RedisQtAddWrite; + m_ctx->ev.delWrite = RedisQtDelWrite; + m_ctx->ev.cleanup = RedisQtCleanup; + return REDIS_OK; + } + + private: + void addRead() { + if (m_read) return; + m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0); + connect(m_read, SIGNAL(activated(int)), this, SLOT(read())); + } + + void delRead() { + if (!m_read) return; + delete m_read; + m_read = 0; + } + + void addWrite() { + if (m_write) return; + m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0); + connect(m_write, SIGNAL(activated(int)), this, SLOT(write())); + } + + void delWrite() { + if (!m_write) return; + delete m_write; + m_write = 0; + } + + void cleanup() { + delRead(); + delWrite(); + } + + private slots: + void read() { redisAsyncHandleRead(m_ctx); } + void write() { redisAsyncHandleWrite(m_ctx); } + + private: + redisAsyncContext * m_ctx; + QSocketNotifier * m_read; + QSocketNotifier * m_write; +}; + +#endif /* !__HIREDIS_QT_H__ */ diff --git a/ext/hiredis-0.14.1/alloc.c b/ext/hiredis-0.14.1/alloc.c new file mode 100644 index 000000000..55c3020e7 --- /dev/null +++ b/ext/hiredis-0.14.1/alloc.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020, Michael Grunder + * + * 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 "fmacros.h" +#include "alloc.h" +#include + +void *hi_malloc(size_t size) { + void *ptr = malloc(size); + if (ptr == NULL) + HIREDIS_OOM_HANDLER; + + return ptr; +} + +void *hi_calloc(size_t nmemb, size_t size) { + void *ptr = calloc(nmemb, size); + if (ptr == NULL) + HIREDIS_OOM_HANDLER; + + return ptr; +} + +void *hi_realloc(void *ptr, size_t size) { + void *newptr = realloc(ptr, size); + if (newptr == NULL) + HIREDIS_OOM_HANDLER; + + return newptr; +} + +char *hi_strdup(const char *str) { + char *newstr = strdup(str); + if (newstr == NULL) + HIREDIS_OOM_HANDLER; + + return newstr; +} diff --git a/ext/hiredis-0.14.1/alloc.h b/ext/hiredis-0.14.1/alloc.h new file mode 100644 index 000000000..2c9b04e35 --- /dev/null +++ b/ext/hiredis-0.14.1/alloc.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020, Michael Grunder + * + * 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. + */ + +#ifndef HIREDIS_ALLOC_H +#define HIREDIS_ALLOC_H + +#include /* for size_t */ + +#ifndef HIREDIS_OOM_HANDLER +#define HIREDIS_OOM_HANDLER abort() +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void *hi_malloc(size_t size); +void *hi_calloc(size_t nmemb, size_t size); +void *hi_realloc(void *ptr, size_t size); +char *hi_strdup(const char *str); + +#ifdef __cplusplus +} +#endif + +#endif /* HIREDIS_ALLOC_H */ diff --git a/ext/hiredis-0.14.1/appveyor.yml b/ext/hiredis-0.14.1/appveyor.yml new file mode 100644 index 000000000..819efbd58 --- /dev/null +++ b/ext/hiredis-0.14.1/appveyor.yml @@ -0,0 +1,23 @@ +# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin) +environment: + matrix: + - CYG_BASH: C:\cygwin64\bin\bash + CC: gcc + - CYG_BASH: C:\cygwin\bin\bash + CC: gcc + TARGET: 32bit + TARGET_VARS: 32bit-vars + +clone_depth: 1 + +# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail +init: + - git config --global core.autocrlf input + +# Install needed build dependencies +install: + - '%CYG_BASH% -lc "cygcheck -dc cygwin"' + +build_script: + - 'echo building...' + - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0 + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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 "fmacros.h" +#include "alloc.h" +#include +#include +#include +#include +#include +#include +#include "async.h" +#include "net.h" +#include "dict.c" +#include "sds.h" + +#define _EL_ADD_READ(ctx) do { \ + if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_READ(ctx) do { \ + if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ + } while(0) +#define _EL_ADD_WRITE(ctx) do { \ + if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_WRITE(ctx) do { \ + if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ + } while(0) +#define _EL_CLEANUP(ctx) do { \ + if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ + } while(0); + +/* Forward declaration of function in hiredis.c */ +int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); + +/* Functions managing dictionary of callbacks for pub/sub. */ +static unsigned int callbackHash(const void *key) { + return dictGenHashFunction((const unsigned char *)key, + sdslen((const sds)key)); +} + +static void *callbackValDup(void *privdata, const void *src) { + ((void) privdata); + redisCallback *dup = hi_malloc(sizeof(*dup)); + memcpy(dup,src,sizeof(*dup)); + return dup; +} + +static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { + int l1, l2; + ((void) privdata); + + l1 = sdslen((const sds)key1); + l2 = sdslen((const sds)key2); + if (l1 != l2) return 0; + return memcmp(key1,key2,l1) == 0; +} + +static void callbackKeyDestructor(void *privdata, void *key) { + ((void) privdata); + sdsfree((sds)key); +} + +static void callbackValDestructor(void *privdata, void *val) { + ((void) privdata); + free(val); +} + +static dictType callbackDict = { + callbackHash, + NULL, + callbackValDup, + callbackKeyCompare, + callbackKeyDestructor, + callbackValDestructor +}; + +static redisAsyncContext *redisAsyncInitialize(redisContext *c) { + redisAsyncContext *ac; + + ac = realloc(c,sizeof(redisAsyncContext)); + if (ac == NULL) + return NULL; + + c = &(ac->c); + + /* The regular connect functions will always set the flag REDIS_CONNECTED. + * For the async API, we want to wait until the first write event is + * received up before setting this flag, so reset it here. */ + c->flags &= ~REDIS_CONNECTED; + + ac->err = 0; + ac->errstr = NULL; + ac->data = NULL; + + ac->ev.data = NULL; + ac->ev.addRead = NULL; + ac->ev.delRead = NULL; + ac->ev.addWrite = NULL; + ac->ev.delWrite = NULL; + ac->ev.cleanup = NULL; + + ac->onConnect = NULL; + ac->onDisconnect = NULL; + + ac->replies.head = NULL; + ac->replies.tail = NULL; + ac->sub.invalid.head = NULL; + ac->sub.invalid.tail = NULL; + ac->sub.channels = dictCreate(&callbackDict,NULL); + ac->sub.patterns = dictCreate(&callbackDict,NULL); + return ac; +} + +/* We want the error field to be accessible directly instead of requiring + * an indirection to the redisContext struct. */ +static void __redisAsyncCopyError(redisAsyncContext *ac) { + if (!ac) + return; + + redisContext *c = &(ac->c); + ac->err = c->err; + ac->errstr = c->errstr; +} + +redisAsyncContext *redisAsyncConnect(const char *ip, int port) { + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectNonBlock(ip,port); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); + if (ac == NULL) { + redisFree(c); + return NULL; + } + + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); + redisAsyncContext *ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); + redisAsyncContext *ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectUnix(const char *path) { + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectUnixNonBlock(path); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); + if (ac == NULL) { + redisFree(c); + return NULL; + } + + __redisAsyncCopyError(ac); + return ac; +} + +int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { + if (ac->onConnect == NULL) { + ac->onConnect = fn; + + /* The common way to detect an established connection is to wait for + * the first write event to be fired. This assumes the related event + * library functions are already set. */ + _EL_ADD_WRITE(ac); + return REDIS_OK; + } + return REDIS_ERR; +} + +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { + if (ac->onDisconnect == NULL) { + ac->onDisconnect = fn; + return REDIS_OK; + } + return REDIS_ERR; +} + +/* Helper functions to push/shift callbacks */ +static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { + redisCallback *cb; + + /* Copy callback from stack to heap */ + cb = malloc(sizeof(*cb)); + if (cb == NULL) + return REDIS_ERR_OOM; + + if (source != NULL) { + memcpy(cb,source,sizeof(*cb)); + cb->next = NULL; + } + + /* Store callback in list */ + if (list->head == NULL) + list->head = cb; + if (list->tail != NULL) + list->tail->next = cb; + list->tail = cb; + return REDIS_OK; +} + +static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { + redisCallback *cb = list->head; + if (cb != NULL) { + list->head = cb->next; + if (cb == list->tail) + list->tail = NULL; + + /* Copy callback from heap to stack */ + if (target != NULL) + memcpy(target,cb,sizeof(*cb)); + free(cb); + return REDIS_OK; + } + return REDIS_ERR; +} + +static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { + redisContext *c = &(ac->c); + if (cb->fn != NULL) { + c->flags |= REDIS_IN_CALLBACK; + cb->fn(ac,reply,cb->privdata); + c->flags &= ~REDIS_IN_CALLBACK; + } +} + +/* Helper function to free the context. */ +static void __redisAsyncFree(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb; + dictIterator *it; + dictEntry *de; + + /* Execute pending callbacks with NULL reply. */ + while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) + __redisRunCallback(ac,&cb,NULL); + + /* Execute callbacks for invalid commands */ + while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) + __redisRunCallback(ac,&cb,NULL); + + /* Run subscription callbacks callbacks with NULL reply */ + it = dictGetIterator(ac->sub.channels); + while ((de = dictNext(it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); + dictReleaseIterator(it); + dictRelease(ac->sub.channels); + + it = dictGetIterator(ac->sub.patterns); + while ((de = dictNext(it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); + dictReleaseIterator(it); + dictRelease(ac->sub.patterns); + + /* Signal event lib to clean up */ + _EL_CLEANUP(ac); + + /* Execute disconnect callback. When redisAsyncFree() initiated destroying + * this context, the status will always be REDIS_OK. */ + if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { + if (c->flags & REDIS_FREEING) { + ac->onDisconnect(ac,REDIS_OK); + } else { + ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); + } + } + + /* Cleanup self */ + redisFree(c); +} + +/* Free the async context. When this function is called from a callback, + * control needs to be returned to redisProcessCallbacks() before actual + * free'ing. To do so, a flag is set on the context which is picked up by + * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ +void redisAsyncFree(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + c->flags |= REDIS_FREEING; + if (!(c->flags & REDIS_IN_CALLBACK)) + __redisAsyncFree(ac); +} + +/* Helper function to make the disconnect happen and clean up. */ +static void __redisAsyncDisconnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + /* Make sure error is accessible if there is any */ + __redisAsyncCopyError(ac); + + if (ac->err == 0) { + /* For clean disconnects, there should be no pending callbacks. */ + int ret = __redisShiftCallback(&ac->replies,NULL); + assert(ret == REDIS_ERR); + } else { + /* Disconnection is caused by an error, make sure that pending + * callbacks cannot call new commands. */ + c->flags |= REDIS_DISCONNECTING; + } + + /* For non-clean disconnects, __redisAsyncFree() will execute pending + * callbacks with a NULL-reply. */ + __redisAsyncFree(ac); +} + +/* Tries to do a clean disconnect from Redis, meaning it stops new commands + * from being issued, but tries to flush the output buffer and execute + * callbacks for all remaining replies. When this function is called from a + * callback, there might be more replies and we can safely defer disconnecting + * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately + * when there are no pending callbacks. */ +void redisAsyncDisconnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + c->flags |= REDIS_DISCONNECTING; + if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) + __redisAsyncDisconnect(ac); +} + +static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { + redisContext *c = &(ac->c); + dict *callbacks; + redisCallback *cb; + dictEntry *de; + int pvariant; + char *stype; + sds sname; + + /* Custom reply functions are not supported for pub/sub. This will fail + * very hard when they are used... */ + if (reply->type == REDIS_REPLY_ARRAY) { + assert(reply->elements >= 2); + assert(reply->element[0]->type == REDIS_REPLY_STRING); + stype = reply->element[0]->str; + pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; + + if (pvariant) + callbacks = ac->sub.patterns; + else + callbacks = ac->sub.channels; + + /* Locate the right callback */ + assert(reply->element[1]->type == REDIS_REPLY_STRING); + sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); + de = dictFind(callbacks,sname); + if (de != NULL) { + cb = dictGetEntryVal(de); + + /* If this is an subscribe reply decrease pending counter. */ + if (strcasecmp(stype+pvariant,"subscribe") == 0) { + cb->pending_subs -= 1; + } + + memcpy(dstcb,cb,sizeof(*dstcb)); + + /* If this is an unsubscribe message, remove it. */ + if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { + if (cb->pending_subs == 0) + dictDelete(callbacks,sname); + + /* If this was the last unsubscribe message, revert to + * non-subscribe mode. */ + assert(reply->element[2]->type == REDIS_REPLY_INTEGER); + + /* Unset subscribed flag only when no pipelined pending subscribe. */ + if (reply->element[2]->integer == 0 + && dictSize(ac->sub.channels) == 0 + && dictSize(ac->sub.patterns) == 0) + c->flags &= ~REDIS_SUBSCRIBED; + } + } + sdsfree(sname); + } else { + /* Shift callback for invalid commands. */ + __redisShiftCallback(&ac->sub.invalid,dstcb); + } + return REDIS_OK; +} + +void redisProcessCallbacks(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb = {NULL, NULL, 0, NULL}; + void *reply = NULL; + int status; + + while((status = redisGetReply(c,&reply)) == REDIS_OK) { + if (reply == NULL) { + /* When the connection is being disconnected and there are + * no more replies, this is the cue to really disconnect. */ + if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0 + && ac->replies.head == NULL) { + __redisAsyncDisconnect(ac); + return; + } + + /* If monitor mode, repush callback */ + if(c->flags & REDIS_MONITORING) { + __redisPushCallback(&ac->replies,&cb); + } + + /* When the connection is not being disconnected, simply stop + * trying to get replies and wait for the next loop tick. */ + break; + } + + /* Even if the context is subscribed, pending regular callbacks will + * get a reply before pub/sub messages arrive. */ + if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { + /* + * A spontaneous reply in a not-subscribed context can be the error + * reply that is sent when a new connection exceeds the maximum + * number of allowed connections on the server side. + * + * This is seen as an error instead of a regular reply because the + * server closes the connection after sending it. + * + * To prevent the error from being overwritten by an EOF error the + * connection is closed here. See issue #43. + * + * Another possibility is that the server is loading its dataset. + * In this case we also want to close the connection, and have the + * user wait until the server is ready to take our request. + */ + if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { + c->err = REDIS_ERR_OTHER; + snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); + c->reader->fn->freeObject(reply); + __redisAsyncDisconnect(ac); + return; + } + /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ + assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); + if(c->flags & REDIS_SUBSCRIBED) + __redisGetSubscribeCallback(ac,reply,&cb); + } + + if (cb.fn != NULL) { + __redisRunCallback(ac,&cb,reply); + c->reader->fn->freeObject(reply); + + /* Proceed with free'ing when redisAsyncFree() was called. */ + if (c->flags & REDIS_FREEING) { + __redisAsyncFree(ac); + return; + } + } else { + /* No callback for this reply. This can either be a NULL callback, + * or there were no callbacks to begin with. Either way, don't + * abort with an error, but simply ignore it because the client + * doesn't know what the server will spit out over the wire. */ + c->reader->fn->freeObject(reply); + } + } + + /* Disconnect when there was an error reading the reply */ + if (status != REDIS_OK) + __redisAsyncDisconnect(ac); +} + +/* Internal helper function to detect socket status the first time a read or + * write event fires. When connecting was not successful, the connect callback + * is called with a REDIS_ERR status and the context is free'd. */ +static int __redisAsyncHandleConnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (redisCheckSocketError(c) == REDIS_ERR) { + /* Try again later when connect(2) is still in progress. */ + if (errno == EINPROGRESS) + return REDIS_OK; + + if (ac->onConnect) ac->onConnect(ac,REDIS_ERR); + __redisAsyncDisconnect(ac); + return REDIS_ERR; + } + + /* Mark context as connected. */ + c->flags |= REDIS_CONNECTED; + if (ac->onConnect) ac->onConnect(ac,REDIS_OK); + return REDIS_OK; +} + +/* This function should be called when the socket is readable. + * It processes all replies that can be read and executes their callbacks. + */ +void redisAsyncHandleRead(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + + if (redisBufferRead(c) == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + /* Always re-schedule reads */ + _EL_ADD_READ(ac); + redisProcessCallbacks(ac); + } +} + +void redisAsyncHandleWrite(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + int done = 0; + + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + + if (redisBufferWrite(c,&done) == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + /* Continue writing when not done, stop writing otherwise */ + if (!done) + _EL_ADD_WRITE(ac); + else + _EL_DEL_WRITE(ac); + + /* Always schedule reads after writes */ + _EL_ADD_READ(ac); + } +} + +/* Sets a pointer to the first argument and its length starting at p. Returns + * the number of bytes to skip to get to the following argument. */ +static const char *nextArgument(const char *start, const char **str, size_t *len) { + const char *p = start; + if (p[0] != '$') { + p = strchr(p,'$'); + if (p == NULL) return NULL; + } + + *len = (int)strtol(p+1,NULL,10); + p = strchr(p,'\r'); + assert(p); + *str = p+2; + return p+2+(*len)+2; +} + +/* Helper function for the redisAsyncCommand* family of functions. Writes a + * formatted command to the output buffer and registers the provided callback + * function with the context. */ +static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { + redisContext *c = &(ac->c); + redisCallback cb; + struct dict *cbdict; + dictEntry *de; + redisCallback *existcb; + int pvariant, hasnext; + const char *cstr, *astr; + size_t clen, alen; + const char *p; + sds sname; + int ret; + + /* Don't accept new commands when the connection is about to be closed. */ + if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; + + /* Setup callback */ + cb.fn = fn; + cb.privdata = privdata; + cb.pending_subs = 1; + + /* Find out which command will be appended. */ + p = nextArgument(cmd,&cstr,&clen); + assert(p != NULL); + hasnext = (p[0] == '$'); + pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; + cstr += pvariant; + clen -= pvariant; + + if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { + c->flags |= REDIS_SUBSCRIBED; + + /* Add every channel/pattern to the list of subscription callbacks. */ + while ((p = nextArgument(p,&astr,&alen)) != NULL) { + sname = sdsnewlen(astr,alen); + if (pvariant) + cbdict = ac->sub.patterns; + else + cbdict = ac->sub.channels; + + de = dictFind(cbdict,sname); + + if (de != NULL) { + existcb = dictGetEntryVal(de); + cb.pending_subs = existcb->pending_subs + 1; + } + + ret = dictReplace(cbdict,sname,&cb); + + if (ret == 0) sdsfree(sname); + } + } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { + /* It is only useful to call (P)UNSUBSCRIBE when the context is + * subscribed to one or more channels or patterns. */ + if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; + + /* (P)UNSUBSCRIBE does not have its own response: every channel or + * pattern that is unsubscribed will receive a message. This means we + * should not append a callback function for this command. */ + } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) { + /* Set monitor flag and push callback */ + c->flags |= REDIS_MONITORING; + __redisPushCallback(&ac->replies,&cb); + } else { + if (c->flags & REDIS_SUBSCRIBED) + /* This will likely result in an error reply, but it needs to be + * received and passed to the callback. */ + __redisPushCallback(&ac->sub.invalid,&cb); + else + __redisPushCallback(&ac->replies,&cb); + } + + __redisAppendCommand(c,cmd,len); + + /* Always schedule a write when the write buffer is non-empty */ + _EL_ADD_WRITE(ac); + + return REDIS_OK; +} + +int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { + char *cmd; + int len; + int status; + len = redisvFormatCommand(&cmd,format,ap); + + /* We don't want to pass -1 or -2 to future functions as a length. */ + if (len < 0) + return REDIS_ERR; + + status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + free(cmd); + return status; +} + +int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { + va_list ap; + int status; + va_start(ap,format); + status = redisvAsyncCommand(ac,fn,privdata,format,ap); + va_end(ap); + return status; +} + +int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { + sds cmd; + int len; + int status; + len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); + if (len < 0) + return REDIS_ERR; + status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + sdsfree(cmd); + return status; +} + +int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { + int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + return status; +} diff --git a/ext/hiredis-0.14.1/async.h b/ext/hiredis-0.14.1/async.h new file mode 100644 index 000000000..e69d84090 --- /dev/null +++ b/ext/hiredis-0.14.1/async.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + +#ifndef __HIREDIS_ASYNC_H +#define __HIREDIS_ASYNC_H +#include "hiredis.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct redisAsyncContext; /* need forward declaration of redisAsyncContext */ +struct dict; /* dictionary header is included in async.c */ + +/* Reply callback prototype and container */ +typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); +typedef struct redisCallback { + struct redisCallback *next; /* simple singly linked list */ + redisCallbackFn *fn; + int pending_subs; + void *privdata; +} redisCallback; + +/* List of callbacks for either regular replies or pub/sub */ +typedef struct redisCallbackList { + redisCallback *head, *tail; +} redisCallbackList; + +/* Connection callback prototypes */ +typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); +typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); + +/* Context for an async connection to Redis */ +typedef struct redisAsyncContext { + /* Hold the regular context, so it can be realloc'ed. */ + redisContext c; + + /* Setup error flags so they can be used directly. */ + int err; + char *errstr; + + /* Not used by hiredis */ + void *data; + + /* Event library data and hooks */ + struct { + void *data; + + /* Hooks that are called when the library expects to start + * reading/writing. These functions should be idempotent. */ + void (*addRead)(void *privdata); + void (*delRead)(void *privdata); + void (*addWrite)(void *privdata); + void (*delWrite)(void *privdata); + void (*cleanup)(void *privdata); + } ev; + + /* Called when either the connection is terminated due to an error or per + * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ + redisDisconnectCallback *onDisconnect; + + /* Called when the first write event was received. */ + redisConnectCallback *onConnect; + + /* Regular command callbacks */ + redisCallbackList replies; + + /* Subscription callbacks */ + struct { + redisCallbackList invalid; + struct dict *channels; + struct dict *patterns; + } sub; +} redisAsyncContext; + +/* Functions that proxy to hiredis */ +redisAsyncContext *redisAsyncConnect(const char *ip, int port); +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); +redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, + const char *source_addr); +redisAsyncContext *redisAsyncConnectUnix(const char *path); +int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); +void redisAsyncDisconnect(redisAsyncContext *ac); +void redisAsyncFree(redisAsyncContext *ac); + +/* Handle read/write events */ +void redisAsyncHandleRead(redisAsyncContext *ac); +void redisAsyncHandleWrite(redisAsyncContext *ac); + +/* Command functions for an async context. Write the command to the + * output buffer and register the provided callback. */ +int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); +int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); +int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); +int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-0.14.1/dict.c b/ext/hiredis-0.14.1/dict.c new file mode 100644 index 000000000..29cdc190f --- /dev/null +++ b/ext/hiredis-0.14.1/dict.c @@ -0,0 +1,339 @@ +/* Hash table implementation. + * + * This file implements in memory hash tables with insert/del/replace/find/ + * get-random-element operations. Hash tables will auto resize if needed + * tables of power of two in size are used, collisions are handled by + * chaining. See the source code for more information... :) + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * 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 "fmacros.h" +#include "alloc.h" +#include +#include +#include +#include "dict.h" + +/* -------------------------- private prototypes ---------------------------- */ + +static int _dictExpandIfNeeded(dict *ht); +static unsigned long _dictNextPower(unsigned long size); +static int _dictKeyIndex(dict *ht, const void *key); +static int _dictInit(dict *ht, dictType *type, void *privDataPtr); + +/* -------------------------- hash functions -------------------------------- */ + +/* Generic hash function (a popular one from Bernstein). + * I tested a few and this was the best. */ +static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { + unsigned int hash = 5381; + + while (len--) + hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ + return hash; +} + +/* ----------------------------- API implementation ------------------------- */ + +/* Reset an hashtable already initialized with ht_init(). + * NOTE: This function should only called by ht_destroy(). */ +static void _dictReset(dict *ht) { + ht->table = NULL; + ht->size = 0; + ht->sizemask = 0; + ht->used = 0; +} + +/* Create a new hash table */ +static dict *dictCreate(dictType *type, void *privDataPtr) { + dict *ht = hi_malloc(sizeof(*ht)); + _dictInit(ht,type,privDataPtr); + return ht; +} + +/* Initialize the hash table */ +static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { + _dictReset(ht); + ht->type = type; + ht->privdata = privDataPtr; + return DICT_OK; +} + +/* Expand or create the hashtable */ +static int dictExpand(dict *ht, unsigned long size) { + dict n; /* the new hashtable */ + unsigned long realsize = _dictNextPower(size), i; + + /* the size is invalid if it is smaller than the number of + * elements already inside the hashtable */ + if (ht->used > size) + return DICT_ERR; + + _dictInit(&n, ht->type, ht->privdata); + n.size = realsize; + n.sizemask = realsize-1; + n.table = calloc(realsize,sizeof(dictEntry*)); + + /* Copy all the elements from the old to the new table: + * note that if the old hash table is empty ht->size is zero, + * so dictExpand just creates an hash table. */ + n.used = ht->used; + for (i = 0; i < ht->size && ht->used > 0; i++) { + dictEntry *he, *nextHe; + + if (ht->table[i] == NULL) continue; + + /* For each hash entry on this slot... */ + he = ht->table[i]; + while(he) { + unsigned int h; + + nextHe = he->next; + /* Get the new element index */ + h = dictHashKey(ht, he->key) & n.sizemask; + he->next = n.table[h]; + n.table[h] = he; + ht->used--; + /* Pass to the next element */ + he = nextHe; + } + } + assert(ht->used == 0); + free(ht->table); + + /* Remap the new hashtable in the old */ + *ht = n; + return DICT_OK; +} + +/* Add an element to the target hash table */ +static int dictAdd(dict *ht, void *key, void *val) { + int index; + dictEntry *entry; + + /* Get the index of the new element, or -1 if + * the element already exists. */ + if ((index = _dictKeyIndex(ht, key)) == -1) + return DICT_ERR; + + /* Allocates the memory and stores key */ + entry = hi_malloc(sizeof(*entry)); + entry->next = ht->table[index]; + ht->table[index] = entry; + + /* Set the hash entry fields. */ + dictSetHashKey(ht, entry, key); + dictSetHashVal(ht, entry, val); + ht->used++; + return DICT_OK; +} + +/* Add an element, discarding the old if the key already exists. + * Return 1 if the key was added from scratch, 0 if there was already an + * element with such key and dictReplace() just performed a value update + * operation. */ +static int dictReplace(dict *ht, void *key, void *val) { + dictEntry *entry, auxentry; + + /* Try to add the element. If the key + * does not exists dictAdd will succeed. */ + if (dictAdd(ht, key, val) == DICT_OK) + return 1; + /* It already exists, get the entry */ + entry = dictFind(ht, key); + /* Free the old value and set the new one */ + /* Set the new value and free the old one. Note that it is important + * to do that in this order, as the value may just be exactly the same + * as the previous one. In this context, think to reference counting, + * you want to increment (set), and then decrement (free), and not the + * reverse. */ + auxentry = *entry; + dictSetHashVal(ht, entry, val); + dictFreeEntryVal(ht, &auxentry); + return 0; +} + +/* Search and remove an element */ +static int dictDelete(dict *ht, const void *key) { + unsigned int h; + dictEntry *de, *prevde; + + if (ht->size == 0) + return DICT_ERR; + h = dictHashKey(ht, key) & ht->sizemask; + de = ht->table[h]; + + prevde = NULL; + while(de) { + if (dictCompareHashKeys(ht,key,de->key)) { + /* Unlink the element from the list */ + if (prevde) + prevde->next = de->next; + else + ht->table[h] = de->next; + + dictFreeEntryKey(ht,de); + dictFreeEntryVal(ht,de); + free(de); + ht->used--; + return DICT_OK; + } + prevde = de; + de = de->next; + } + return DICT_ERR; /* not found */ +} + +/* Destroy an entire hash table */ +static int _dictClear(dict *ht) { + unsigned long i; + + /* Free all the elements */ + for (i = 0; i < ht->size && ht->used > 0; i++) { + dictEntry *he, *nextHe; + + if ((he = ht->table[i]) == NULL) continue; + while(he) { + nextHe = he->next; + dictFreeEntryKey(ht, he); + dictFreeEntryVal(ht, he); + free(he); + ht->used--; + he = nextHe; + } + } + /* Free the table and the allocated cache structure */ + free(ht->table); + /* Re-initialize the table */ + _dictReset(ht); + return DICT_OK; /* never fails */ +} + +/* Clear & Release the hash table */ +static void dictRelease(dict *ht) { + _dictClear(ht); + free(ht); +} + +static dictEntry *dictFind(dict *ht, const void *key) { + dictEntry *he; + unsigned int h; + + if (ht->size == 0) return NULL; + h = dictHashKey(ht, key) & ht->sizemask; + he = ht->table[h]; + while(he) { + if (dictCompareHashKeys(ht, key, he->key)) + return he; + he = he->next; + } + return NULL; +} + +static dictIterator *dictGetIterator(dict *ht) { + dictIterator *iter = hi_malloc(sizeof(*iter)); + + iter->ht = ht; + iter->index = -1; + iter->entry = NULL; + iter->nextEntry = NULL; + return iter; +} + +static dictEntry *dictNext(dictIterator *iter) { + while (1) { + if (iter->entry == NULL) { + iter->index++; + if (iter->index >= + (signed)iter->ht->size) break; + iter->entry = iter->ht->table[iter->index]; + } else { + iter->entry = iter->nextEntry; + } + if (iter->entry) { + /* We need to save the 'next' here, the iterator user + * may delete the entry we are returning. */ + iter->nextEntry = iter->entry->next; + return iter->entry; + } + } + return NULL; +} + +static void dictReleaseIterator(dictIterator *iter) { + free(iter); +} + +/* ------------------------- private functions ------------------------------ */ + +/* Expand the hash table if needed */ +static int _dictExpandIfNeeded(dict *ht) { + /* If the hash table is empty expand it to the initial size, + * if the table is "full" dobule its size. */ + if (ht->size == 0) + return dictExpand(ht, DICT_HT_INITIAL_SIZE); + if (ht->used == ht->size) + return dictExpand(ht, ht->size*2); + return DICT_OK; +} + +/* Our hash table capability is a power of two */ +static unsigned long _dictNextPower(unsigned long size) { + unsigned long i = DICT_HT_INITIAL_SIZE; + + if (size >= LONG_MAX) return LONG_MAX; + while(1) { + if (i >= size) + return i; + i *= 2; + } +} + +/* Returns the index of a free slot that can be populated with + * an hash entry for the given 'key'. + * If the key already exists, -1 is returned. */ +static int _dictKeyIndex(dict *ht, const void *key) { + unsigned int h; + dictEntry *he; + + /* Expand the hashtable if needed */ + if (_dictExpandIfNeeded(ht) == DICT_ERR) + return -1; + /* Compute the key hash value */ + h = dictHashKey(ht, key) & ht->sizemask; + /* Search if this slot does not already contain the given key */ + he = ht->table[h]; + while(he) { + if (dictCompareHashKeys(ht, key, he->key)) + return -1; + he = he->next; + } + return h; +} + diff --git a/ext/hiredis-0.14.1/dict.h b/ext/hiredis-0.14.1/dict.h new file mode 100644 index 000000000..95fcd280e --- /dev/null +++ b/ext/hiredis-0.14.1/dict.h @@ -0,0 +1,126 @@ +/* Hash table implementation. + * + * This file implements in memory hash tables with insert/del/replace/find/ + * get-random-element operations. Hash tables will auto resize if needed + * tables of power of two in size are used, collisions are handled by + * chaining. See the source code for more information... :) + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * 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. + */ + +#ifndef __DICT_H +#define __DICT_H + +#define DICT_OK 0 +#define DICT_ERR 1 + +/* Unused arguments generate annoying warnings... */ +#define DICT_NOTUSED(V) ((void) V) + +typedef struct dictEntry { + void *key; + void *val; + struct dictEntry *next; +} dictEntry; + +typedef struct dictType { + unsigned int (*hashFunction)(const void *key); + void *(*keyDup)(void *privdata, const void *key); + void *(*valDup)(void *privdata, const void *obj); + int (*keyCompare)(void *privdata, const void *key1, const void *key2); + void (*keyDestructor)(void *privdata, void *key); + void (*valDestructor)(void *privdata, void *obj); +} dictType; + +typedef struct dict { + dictEntry **table; + dictType *type; + unsigned long size; + unsigned long sizemask; + unsigned long used; + void *privdata; +} dict; + +typedef struct dictIterator { + dict *ht; + int index; + dictEntry *entry, *nextEntry; +} dictIterator; + +/* This is the initial size of every hash table */ +#define DICT_HT_INITIAL_SIZE 4 + +/* ------------------------------- Macros ------------------------------------*/ +#define dictFreeEntryVal(ht, entry) \ + if ((ht)->type->valDestructor) \ + (ht)->type->valDestructor((ht)->privdata, (entry)->val) + +#define dictSetHashVal(ht, entry, _val_) do { \ + if ((ht)->type->valDup) \ + entry->val = (ht)->type->valDup((ht)->privdata, _val_); \ + else \ + entry->val = (_val_); \ +} while(0) + +#define dictFreeEntryKey(ht, entry) \ + if ((ht)->type->keyDestructor) \ + (ht)->type->keyDestructor((ht)->privdata, (entry)->key) + +#define dictSetHashKey(ht, entry, _key_) do { \ + if ((ht)->type->keyDup) \ + entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ + else \ + entry->key = (_key_); \ +} while(0) + +#define dictCompareHashKeys(ht, key1, key2) \ + (((ht)->type->keyCompare) ? \ + (ht)->type->keyCompare((ht)->privdata, key1, key2) : \ + (key1) == (key2)) + +#define dictHashKey(ht, key) (ht)->type->hashFunction(key) + +#define dictGetEntryKey(he) ((he)->key) +#define dictGetEntryVal(he) ((he)->val) +#define dictSlots(ht) ((ht)->size) +#define dictSize(ht) ((ht)->used) + +/* API */ +static unsigned int dictGenHashFunction(const unsigned char *buf, int len); +static dict *dictCreate(dictType *type, void *privDataPtr); +static int dictExpand(dict *ht, unsigned long size); +static int dictAdd(dict *ht, void *key, void *val); +static int dictReplace(dict *ht, void *key, void *val); +static int dictDelete(dict *ht, const void *key); +static void dictRelease(dict *ht); +static dictEntry * dictFind(dict *ht, const void *key); +static dictIterator *dictGetIterator(dict *ht); +static dictEntry *dictNext(dictIterator *iter); +static void dictReleaseIterator(dictIterator *iter); + +#endif /* __DICT_H */ diff --git a/ext/hiredis-0.14.1/examples/example-ae.c b/ext/hiredis-0.14.1/examples/example-ae.c new file mode 100644 index 000000000..8efa7306a --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-ae.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +#include +#include +#include + +/* Put event loop in the global scope, so it can be explicitly stopped */ +static aeEventLoop *loop; + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + aeStop(loop); + return; + } + + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + aeStop(loop); + return; + } + + printf("Disconnected...\n"); + aeStop(loop); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + loop = aeCreateEventLoop(64); + redisAeAttach(loop, c); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + aeMain(loop); + return 0; +} + diff --git a/ext/hiredis-0.14.1/examples/example-glib.c b/ext/hiredis-0.14.1/examples/example-glib.c new file mode 100644 index 000000000..d6e10f8e8 --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-glib.c @@ -0,0 +1,73 @@ +#include + +#include +#include +#include + +static GMainLoop *mainloop; + +static void +connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, + int status) +{ + if (status != REDIS_OK) { + g_printerr("Failed to connect: %s\n", ac->errstr); + g_main_loop_quit(mainloop); + } else { + g_printerr("Connected...\n"); + } +} + +static void +disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, + int status) +{ + if (status != REDIS_OK) { + g_error("Failed to disconnect: %s", ac->errstr); + } else { + g_printerr("Disconnected...\n"); + g_main_loop_quit(mainloop); + } +} + +static void +command_cb(redisAsyncContext *ac, + gpointer r, + gpointer user_data G_GNUC_UNUSED) +{ + redisReply *reply = r; + + if (reply) { + g_print("REPLY: %s\n", reply->str); + } + + redisAsyncDisconnect(ac); +} + +gint +main (gint argc G_GNUC_UNUSED, + gchar *argv[] G_GNUC_UNUSED) +{ + redisAsyncContext *ac; + GMainContext *context = NULL; + GSource *source; + + ac = redisAsyncConnect("127.0.0.1", 6379); + if (ac->err) { + g_printerr("%s\n", ac->errstr); + exit(EXIT_FAILURE); + } + + source = redis_source_new(ac); + mainloop = g_main_loop_new(context, FALSE); + g_source_attach(source, context); + + redisAsyncSetConnectCallback(ac, connect_cb); + redisAsyncSetDisconnectCallback(ac, disconnect_cb); + redisAsyncCommand(ac, command_cb, NULL, "SET key 1234"); + redisAsyncCommand(ac, command_cb, NULL, "GET key"); + + g_main_loop_run(mainloop); + + return EXIT_SUCCESS; +} diff --git a/ext/hiredis-0.14.1/examples/example-ivykis.c b/ext/hiredis-0.14.1/examples/example-ivykis.c new file mode 100644 index 000000000..67affcef3 --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-ivykis.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + iv_init(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisIvykisAttach(c); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + + iv_main(); + + iv_deinit(); + + return 0; +} diff --git a/ext/hiredis-0.14.1/examples/example-libev.c b/ext/hiredis-0.14.1/examples/example-libev.c new file mode 100644 index 000000000..cc8b166ec --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-libev.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibevAttach(EV_DEFAULT_ c); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + ev_loop(EV_DEFAULT_ 0); + return 0; +} diff --git a/ext/hiredis-0.14.1/examples/example-libevent.c b/ext/hiredis-0.14.1/examples/example-libevent.c new file mode 100644 index 000000000..d333c22b7 --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-libevent.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + struct event_base *base = event_base_new(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibeventAttach(c,base); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + event_base_dispatch(base); + return 0; +} diff --git a/ext/hiredis-0.14.1/examples/example-libuv.c b/ext/hiredis-0.14.1/examples/example-libuv.c new file mode 100644 index 000000000..a5462d410 --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-libuv.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + uv_loop_t* loop = uv_default_loop(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibuvAttach(c,loop); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + uv_run(loop, UV_RUN_DEFAULT); + return 0; +} diff --git a/ext/hiredis-0.14.1/examples/example-macosx.c b/ext/hiredis-0.14.1/examples/example-macosx.c new file mode 100644 index 000000000..bc84ed5ba --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-macosx.c @@ -0,0 +1,66 @@ +// +// Created by Дмитрий Бахвалов on 13.07.15. +// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. +// + +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + CFRunLoopStop(CFRunLoopGetCurrent()); + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + CFRunLoopRef loop = CFRunLoopGetCurrent(); + if( !loop ) { + printf("Error: Cannot get current run loop\n"); + return 1; + } + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisMacOSAttach(c, loop); + + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + + CFRunLoopRun(); + + return 0; +} + diff --git a/ext/hiredis-0.14.1/examples/example-qt.cpp b/ext/hiredis-0.14.1/examples/example-qt.cpp new file mode 100644 index 000000000..f524c3f3d --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-qt.cpp @@ -0,0 +1,46 @@ +#include +using namespace std; + +#include +#include + +#include "example-qt.h" + +void getCallback(redisAsyncContext *, void * r, void * privdata) { + + redisReply * reply = static_cast(r); + ExampleQt * ex = static_cast(privdata); + if (reply == nullptr || ex == nullptr) return; + + cout << "key: " << reply->str << endl; + + ex->finish(); +} + +void ExampleQt::run() { + + m_ctx = redisAsyncConnect("localhost", 6379); + + if (m_ctx->err) { + cerr << "Error: " << m_ctx->errstr << endl; + redisAsyncFree(m_ctx); + emit finished(); + } + + m_adapter.setContext(m_ctx); + + redisAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value); + redisAsyncCommand(m_ctx, getCallback, this, "GET key"); +} + +int main (int argc, char **argv) { + + QCoreApplication app(argc, argv); + + ExampleQt example(argv[argc-1]); + + QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit())); + QTimer::singleShot(0, &example, SLOT(run())); + + return app.exec(); +} diff --git a/ext/hiredis-0.14.1/examples/example-qt.h b/ext/hiredis-0.14.1/examples/example-qt.h new file mode 100644 index 000000000..374f47666 --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-qt.h @@ -0,0 +1,32 @@ +#ifndef __HIREDIS_EXAMPLE_QT_H +#define __HIREDIS_EXAMPLE_QT_H + +#include + +class ExampleQt : public QObject { + + Q_OBJECT + + public: + ExampleQt(const char * value, QObject * parent = 0) + : QObject(parent), m_value(value) {} + + signals: + void finished(); + + public slots: + void run(); + + private: + void finish() { emit finished(); } + + private: + const char * m_value; + redisAsyncContext * m_ctx; + RedisQtAdapter m_adapter; + + friend + void getCallback(redisAsyncContext *, void *, void *); +}; + +#endif /* !__HIREDIS_EXAMPLE_QT_H */ diff --git a/ext/hiredis-0.14.1/examples/example.c b/ext/hiredis-0.14.1/examples/example.c new file mode 100644 index 000000000..4d494c55a --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example.c @@ -0,0 +1,78 @@ +#include +#include +#include + +#include + +int main(int argc, char **argv) { + unsigned int j; + redisContext *c; + redisReply *reply; + const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; + int port = (argc > 2) ? atoi(argv[2]) : 6379; + + struct timeval timeout = { 1, 500000 }; // 1.5 seconds + c = redisConnectWithTimeout(hostname, port, timeout); + if (c == NULL || c->err) { + if (c) { + printf("Connection error: %s\n", c->errstr); + redisFree(c); + } else { + printf("Connection error: can't allocate redis context\n"); + } + exit(1); + } + + /* PING server */ + reply = redisCommand(c,"PING"); + printf("PING: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key */ + reply = redisCommand(c,"SET %s %s", "foo", "hello world"); + printf("SET: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key using binary safe API */ + reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); + printf("SET (binary API): %s\n", reply->str); + freeReplyObject(reply); + + /* Try a GET and two INCR */ + reply = redisCommand(c,"GET foo"); + printf("GET foo: %s\n", reply->str); + freeReplyObject(reply); + + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + /* again ... */ + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + + /* Create a list of numbers, from 0 to 9 */ + reply = redisCommand(c,"DEL mylist"); + freeReplyObject(reply); + for (j = 0; j < 10; j++) { + char buf[64]; + + snprintf(buf,64,"%u",j); + reply = redisCommand(c,"LPUSH mylist element-%s", buf); + freeReplyObject(reply); + } + + /* Let's check what we have inside the list */ + reply = redisCommand(c,"LRANGE mylist 0 -1"); + if (reply->type == REDIS_REPLY_ARRAY) { + for (j = 0; j < reply->elements; j++) { + printf("%u) %s\n", j, reply->element[j]->str); + } + } + freeReplyObject(reply); + + /* Disconnects and frees the context */ + redisFree(c); + + return 0; +} diff --git a/ext/hiredis-0.14.1/fmacros.h b/ext/hiredis-0.14.1/fmacros.h new file mode 100644 index 000000000..3227faafd --- /dev/null +++ b/ext/hiredis-0.14.1/fmacros.h @@ -0,0 +1,12 @@ +#ifndef __HIREDIS_FMACRO_H +#define __HIREDIS_FMACRO_H + +#define _XOPEN_SOURCE 600 +#define _POSIX_C_SOURCE 200112L + +#if defined(__APPLE__) && defined(__MACH__) +/* Enable TCP_KEEPALIVE */ +#define _DARWIN_C_SOURCE +#endif + +#endif diff --git a/ext/hiredis-0.14.1/hiredis.c b/ext/hiredis-0.14.1/hiredis.c new file mode 100644 index 000000000..98f43c993 --- /dev/null +++ b/ext/hiredis-0.14.1/hiredis.c @@ -0,0 +1,1006 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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 "fmacros.h" +#include +#include +#include +#include +#include +#include + +#include "hiredis.h" +#include "net.h" +#include "sds.h" + +static redisReply *createReplyObject(int type); +static void *createStringObject(const redisReadTask *task, char *str, size_t len); +static void *createArrayObject(const redisReadTask *task, int elements); +static void *createIntegerObject(const redisReadTask *task, long long value); +static void *createNilObject(const redisReadTask *task); + +/* Default set of functions to build the reply. Keep in mind that such a + * function returning NULL is interpreted as OOM. */ +static redisReplyObjectFunctions defaultFunctions = { + createStringObject, + createArrayObject, + createIntegerObject, + createNilObject, + freeReplyObject +}; + +/* Create a reply object */ +static redisReply *createReplyObject(int type) { + redisReply *r = calloc(1,sizeof(*r)); + + if (r == NULL) + return NULL; + + r->type = type; + return r; +} + +/* Free a reply object */ +void freeReplyObject(void *reply) { + redisReply *r = reply; + size_t j; + + if (r == NULL) + return; + + switch(r->type) { + case REDIS_REPLY_INTEGER: + break; /* Nothing to free */ + case REDIS_REPLY_ARRAY: + if (r->element != NULL) { + for (j = 0; j < r->elements; j++) + freeReplyObject(r->element[j]); + free(r->element); + } + break; + case REDIS_REPLY_ERROR: + case REDIS_REPLY_STATUS: + case REDIS_REPLY_STRING: + free(r->str); + break; + } + free(r); +} + +static void *createStringObject(const redisReadTask *task, char *str, size_t len) { + redisReply *r, *parent; + char *buf; + + r = createReplyObject(task->type); + if (r == NULL) + return NULL; + + buf = malloc(len+1); + if (buf == NULL) { + freeReplyObject(r); + return NULL; + } + + assert(task->type == REDIS_REPLY_ERROR || + task->type == REDIS_REPLY_STATUS || + task->type == REDIS_REPLY_STRING); + + /* Copy string value */ + memcpy(buf,str,len); + buf[len] = '\0'; + r->str = buf; + r->len = len; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createArrayObject(const redisReadTask *task, int elements) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_ARRAY); + if (r == NULL) + return NULL; + + if (elements > 0) { + r->element = calloc(elements,sizeof(redisReply*)); + if (r->element == NULL) { + freeReplyObject(r); + return NULL; + } + } + + r->elements = elements; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createIntegerObject(const redisReadTask *task, long long value) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_INTEGER); + if (r == NULL) + return NULL; + + r->integer = value; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createNilObject(const redisReadTask *task) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_NIL); + if (r == NULL) + return NULL; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +/* Return the number of digits of 'v' when converted to string in radix 10. + * Implementation borrowed from link in redis/src/util.c:string2ll(). */ +static uint32_t countDigits(uint64_t v) { + uint32_t result = 1; + for (;;) { + if (v < 10) return result; + if (v < 100) return result + 1; + if (v < 1000) return result + 2; + if (v < 10000) return result + 3; + v /= 10000U; + result += 4; + } +} + +/* Helper that calculates the bulk length given a certain string length. */ +static size_t bulklen(size_t len) { + return 1+countDigits(len)+2+len+2; +} + +int redisvFormatCommand(char **target, const char *format, va_list ap) { + const char *c = format; + char *cmd = NULL; /* final command */ + int pos; /* position in final command */ + sds curarg, newarg; /* current argument */ + int touched = 0; /* was the current argument touched? */ + char **curargv = NULL, **newargv = NULL; + int argc = 0; + int totlen = 0; + int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */ + int j; + + /* Abort if there is not target to set */ + if (target == NULL) + return -1; + + /* Build the command string accordingly to protocol */ + curarg = sdsempty(); + if (curarg == NULL) + return -1; + + while(*c != '\0') { + if (*c != '%' || c[1] == '\0') { + if (*c == ' ') { + if (touched) { + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto memory_err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); + + /* curarg is put in argv so it can be overwritten. */ + curarg = sdsempty(); + if (curarg == NULL) goto memory_err; + touched = 0; + } + } else { + newarg = sdscatlen(curarg,c,1); + if (newarg == NULL) goto memory_err; + curarg = newarg; + touched = 1; + } + } else { + char *arg; + size_t size; + + /* Set newarg so it can be checked even if it is not touched. */ + newarg = curarg; + + switch(c[1]) { + case 's': + arg = va_arg(ap,char*); + size = strlen(arg); + if (size > 0) + newarg = sdscatlen(curarg,arg,size); + break; + case 'b': + arg = va_arg(ap,char*); + size = va_arg(ap,size_t); + if (size > 0) + newarg = sdscatlen(curarg,arg,size); + break; + case '%': + newarg = sdscat(curarg,"%"); + break; + default: + /* Try to detect printf format */ + { + static const char intfmts[] = "diouxX"; + static const char flags[] = "#0-+ "; + char _format[16]; + const char *_p = c+1; + size_t _l = 0; + va_list _cpy; + + /* Flags */ + while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; + + /* Field width */ + while (*_p != '\0' && isdigit(*_p)) _p++; + + /* Precision */ + if (*_p == '.') { + _p++; + while (*_p != '\0' && isdigit(*_p)) _p++; + } + + /* Copy va_list before consuming with va_arg */ + va_copy(_cpy,ap); + + /* Integer conversion (without modifiers) */ + if (strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); + goto fmt_valid; + } + + /* Double conversion (without modifiers) */ + if (strchr("eEfFgGaA",*_p) != NULL) { + va_arg(ap,double); + goto fmt_valid; + } + + /* Size: char */ + if (_p[0] == 'h' && _p[1] == 'h') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* char gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: short */ + if (_p[0] == 'h') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* short gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long long */ + if (_p[0] == 'l' && _p[1] == 'l') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long long); + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long */ + if (_p[0] == 'l') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long); + goto fmt_valid; + } + goto fmt_invalid; + } + + fmt_invalid: + va_end(_cpy); + goto format_err; + + fmt_valid: + _l = (_p+1)-c; + if (_l < sizeof(_format)-2) { + memcpy(_format,c,_l); + _format[_l] = '\0'; + newarg = sdscatvprintf(curarg,_format,_cpy); + + /* Update current position (note: outer blocks + * increment c twice so compensate here) */ + c = _p-1; + } + + va_end(_cpy); + break; + } + } + + if (newarg == NULL) goto memory_err; + curarg = newarg; + + touched = 1; + c++; + } + c++; + } + + /* Add the last argument if needed */ + if (touched) { + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto memory_err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); + } else { + sdsfree(curarg); + } + + /* Clear curarg because it was put in curargv or was free'd. */ + curarg = NULL; + + /* Add bytes needed to hold multi bulk count */ + totlen += 1+countDigits(argc)+2; + + /* Build the command at protocol level */ + cmd = malloc(totlen+1); + if (cmd == NULL) goto memory_err; + + pos = sprintf(cmd,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); + memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); + pos += sdslen(curargv[j]); + sdsfree(curargv[j]); + cmd[pos++] = '\r'; + cmd[pos++] = '\n'; + } + assert(pos == totlen); + cmd[pos] = '\0'; + + free(curargv); + *target = cmd; + return totlen; + +format_err: + error_type = -2; + goto cleanup; + +memory_err: + error_type = -1; + goto cleanup; + +cleanup: + if (curargv) { + while(argc--) + sdsfree(curargv[argc]); + free(curargv); + } + + sdsfree(curarg); + free(cmd); + + return error_type; +} + +/* Format a command according to the Redis protocol. This function + * takes a format similar to printf: + * + * %s represents a C null terminated string you want to interpolate + * %b represents a binary safe string + * + * When using %b you need to provide both the pointer to the string + * and the length in bytes as a size_t. Examples: + * + * len = redisFormatCommand(target, "GET %s", mykey); + * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); + */ +int redisFormatCommand(char **target, const char *format, ...) { + va_list ap; + int len; + va_start(ap,format); + len = redisvFormatCommand(target,format,ap); + va_end(ap); + + /* The API says "-1" means bad result, but we now also return "-2" in some + * cases. Force the return value to always be -1. */ + if (len < 0) + len = -1; + + return len; +} + +/* Format a command according to the Redis protocol using an sds string and + * sdscatfmt for the processing of arguments. This function takes the + * number of arguments, an array with arguments and an array with their + * lengths. If the latter is set to NULL, strlen will be used to compute the + * argument lengths. + */ +int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, + const size_t *argvlen) +{ + sds cmd; + unsigned long long totlen; + int j; + size_t len; + + /* Abort on a NULL target */ + if (target == NULL) + return -1; + + /* Calculate our total size */ + totlen = 1+countDigits(argc)+2; + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + totlen += bulklen(len); + } + + /* Use an SDS string for command construction */ + cmd = sdsempty(); + if (cmd == NULL) + return -1; + + /* We already know how much storage we need */ + cmd = sdsMakeRoomFor(cmd, totlen); + if (cmd == NULL) + return -1; + + /* Construct command */ + cmd = sdscatfmt(cmd, "*%i\r\n", argc); + for (j=0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + cmd = sdscatfmt(cmd, "$%u\r\n", len); + cmd = sdscatlen(cmd, argv[j], len); + cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); + } + + assert(sdslen(cmd)==totlen); + + *target = cmd; + return totlen; +} + +void redisFreeSdsCommand(sds cmd) { + sdsfree(cmd); +} + +/* Format a command according to the Redis protocol. This function takes the + * number of arguments, an array with arguments and an array with their + * lengths. If the latter is set to NULL, strlen will be used to compute the + * argument lengths. + */ +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { + char *cmd = NULL; /* final command */ + int pos; /* position in final command */ + size_t len; + int totlen, j; + + /* Abort on a NULL target */ + if (target == NULL) + return -1; + + /* Calculate number of bytes needed for the command */ + totlen = 1+countDigits(argc)+2; + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + totlen += bulklen(len); + } + + /* Build the command at protocol level */ + cmd = malloc(totlen+1); + if (cmd == NULL) + return -1; + + pos = sprintf(cmd,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + pos += sprintf(cmd+pos,"$%zu\r\n",len); + memcpy(cmd+pos,argv[j],len); + pos += len; + cmd[pos++] = '\r'; + cmd[pos++] = '\n'; + } + assert(pos == totlen); + cmd[pos] = '\0'; + + *target = cmd; + return totlen; +} + +void redisFreeCommand(char *cmd) { + free(cmd); +} + +void __redisSetError(redisContext *c, int type, const char *str) { + size_t len; + + c->err = type; + if (str != NULL) { + len = strlen(str); + len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); + memcpy(c->errstr,str,len); + c->errstr[len] = '\0'; + } else { + /* Only REDIS_ERR_IO may lack a description! */ + assert(type == REDIS_ERR_IO); + strerror_r(errno, c->errstr, sizeof(c->errstr)); + } +} + +redisReader *redisReaderCreate(void) { + return redisReaderCreateWithFunctions(&defaultFunctions); +} + +static redisContext *redisContextInit(void) { + redisContext *c; + + c = calloc(1,sizeof(redisContext)); + if (c == NULL) + return NULL; + + c->obuf = sdsempty(); + c->reader = redisReaderCreate(); + + if (c->obuf == NULL || c->reader == NULL) { + redisFree(c); + return NULL; + } + + return c; +} + +void redisFree(redisContext *c) { + if (c == NULL) + return; + if (c->fd > 0) + close(c->fd); + sdsfree(c->obuf); + redisReaderFree(c->reader); + free(c->tcp.host); + free(c->tcp.source_addr); + free(c->unix_sock.path); + free(c->timeout); + free(c); +} + +int redisFreeKeepFd(redisContext *c) { + int fd = c->fd; + c->fd = -1; + redisFree(c); + return fd; +} + +int redisReconnect(redisContext *c) { + c->err = 0; + memset(c->errstr, '\0', strlen(c->errstr)); + + if (c->fd > 0) { + close(c->fd); + } + + sdsfree(c->obuf); + redisReaderFree(c->reader); + + c->obuf = sdsempty(); + c->reader = redisReaderCreate(); + + if (c->connection_type == REDIS_CONN_TCP) { + return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port, + c->timeout, c->tcp.source_addr); + } else if (c->connection_type == REDIS_CONN_UNIX) { + return redisContextConnectUnix(c, c->unix_sock.path, c->timeout); + } else { + /* Something bad happened here and shouldn't have. There isn't + enough information in the context to reconnect. */ + __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect"); + } + + return REDIS_ERR; +} + +/* Connect to a Redis instance. On error the field error in the returned + * context will be set to the return value of the error function. + * When no set of reply functions is given, the default set will be used. */ +redisContext *redisConnect(const char *ip, int port) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,NULL); + return c; +} + +redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,&tv); + return c; +} + +redisContext *redisConnectNonBlock(const char *ip, int port) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags &= ~REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,NULL); + return c; +} + +redisContext *redisConnectBindNonBlock(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisContextInit(); + if (c == NULL) + return NULL; + c->flags &= ~REDIS_BLOCK; + redisContextConnectBindTcp(c,ip,port,NULL,source_addr); + return c; +} + +redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisContextInit(); + if (c == NULL) + return NULL; + c->flags &= ~REDIS_BLOCK; + c->flags |= REDIS_REUSEADDR; + redisContextConnectBindTcp(c,ip,port,NULL,source_addr); + return c; +} + +redisContext *redisConnectUnix(const char *path) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectUnix(c,path,NULL); + return c; +} + +redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectUnix(c,path,&tv); + return c; +} + +redisContext *redisConnectUnixNonBlock(const char *path) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags &= ~REDIS_BLOCK; + redisContextConnectUnix(c,path,NULL); + return c; +} + +redisContext *redisConnectFd(int fd) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->fd = fd; + c->flags |= REDIS_BLOCK | REDIS_CONNECTED; + return c; +} + +/* Set read/write timeout on a blocking socket. */ +int redisSetTimeout(redisContext *c, const struct timeval tv) { + if (c->flags & REDIS_BLOCK) + return redisContextSetTimeout(c,tv); + return REDIS_ERR; +} + +/* Enable connection KeepAlive. */ +int redisEnableKeepAlive(redisContext *c) { + if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK) + return REDIS_ERR; + return REDIS_OK; +} + +/* Use this function to handle a read event on the descriptor. It will try + * and read some bytes from the socket and feed them to the reply parser. + * + * After this function is called, you may use redisContextReadReply to + * see if there is a reply available. */ +int redisBufferRead(redisContext *c) { + char buf[1024*16]; + int nread; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + + nread = read(c->fd,buf,sizeof(buf)); + if (nread == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + } else if (nread == 0) { + __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); + return REDIS_ERR; + } else { + if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + } + return REDIS_OK; +} + +/* Write the output buffer to the socket. + * + * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was + * successfully written to the socket. When the buffer is empty after the + * write operation, "done" is set to 1 (if given). + * + * Returns REDIS_ERR if an error occurred trying to write and sets + * c->errstr to hold the appropriate error string. + */ +int redisBufferWrite(redisContext *c, int *done) { + int nwritten; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + + if (sdslen(c->obuf) > 0) { + nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); + if (nwritten == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + } else if (nwritten > 0) { + if (nwritten == (signed)sdslen(c->obuf)) { + sdsfree(c->obuf); + c->obuf = sdsempty(); + } else { + sdsrange(c->obuf,nwritten,-1); + } + } + } + if (done != NULL) *done = (sdslen(c->obuf) == 0); + return REDIS_OK; +} + +/* Internal helper function to try and get a reply from the reader, + * or set an error in the context otherwise. */ +int redisGetReplyFromReader(redisContext *c, void **reply) { + if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + return REDIS_OK; +} + +int redisGetReply(redisContext *c, void **reply) { + int wdone = 0; + void *aux = NULL; + + /* Try to read pending replies */ + if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; + + /* For the blocking context, flush output buffer and read reply */ + if (aux == NULL && c->flags & REDIS_BLOCK) { + /* Write until done */ + do { + if (redisBufferWrite(c,&wdone) == REDIS_ERR) + return REDIS_ERR; + } while (!wdone); + + /* Read until there is a reply */ + do { + if (redisBufferRead(c) == REDIS_ERR) + return REDIS_ERR; + if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; + } while (aux == NULL); + } + + /* Set reply object */ + if (reply != NULL) *reply = aux; + return REDIS_OK; +} + + +/* Helper function for the redisAppendCommand* family of functions. + * + * Write a formatted command to the output buffer. When this family + * is used, you need to call redisGetReply yourself to retrieve + * the reply (or replies in pub/sub). + */ +int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { + sds newbuf; + + newbuf = sdscatlen(c->obuf,cmd,len); + if (newbuf == NULL) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + c->obuf = newbuf; + return REDIS_OK; +} + +int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) { + + if (__redisAppendCommand(c, cmd, len) != REDIS_OK) { + return REDIS_ERR; + } + + return REDIS_OK; +} + +int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { + char *cmd; + int len; + + len = redisvFormatCommand(&cmd,format,ap); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } else if (len == -2) { + __redisSetError(c,REDIS_ERR_OTHER,"Invalid format string"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + free(cmd); + return REDIS_ERR; + } + + free(cmd); + return REDIS_OK; +} + +int redisAppendCommand(redisContext *c, const char *format, ...) { + va_list ap; + int ret; + + va_start(ap,format); + ret = redisvAppendCommand(c,format,ap); + va_end(ap); + return ret; +} + +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { + sds cmd; + int len; + + len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + sdsfree(cmd); + return REDIS_ERR; + } + + sdsfree(cmd); + return REDIS_OK; +} + +/* Helper function for the redisCommand* family of functions. + * + * Write a formatted command to the output buffer. If the given context is + * blocking, immediately read the reply into the "reply" pointer. When the + * context is non-blocking, the "reply" pointer will not be used and the + * command is simply appended to the write buffer. + * + * Returns the reply when a reply was successfully retrieved. Returns NULL + * otherwise. When NULL is returned in a blocking context, the error field + * in the context will be set. + */ +static void *__redisBlockForReply(redisContext *c) { + void *reply; + + if (c->flags & REDIS_BLOCK) { + if (redisGetReply(c,&reply) != REDIS_OK) + return NULL; + return reply; + } + return NULL; +} + +void *redisvCommand(redisContext *c, const char *format, va_list ap) { + if (redisvAppendCommand(c,format,ap) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); +} + +void *redisCommand(redisContext *c, const char *format, ...) { + va_list ap; + va_start(ap,format); + void *reply = redisvCommand(c,format,ap); + va_end(ap); + return reply; +} + +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { + if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); +} diff --git a/ext/hiredis-0.14.1/hiredis.h b/ext/hiredis-0.14.1/hiredis.h new file mode 100644 index 000000000..d945bf204 --- /dev/null +++ b/ext/hiredis-0.14.1/hiredis.h @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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. + */ + +#ifndef __HIREDIS_H +#define __HIREDIS_H +#include "read.h" +#include /* for va_list */ +#include /* for struct timeval */ +#include /* uintXX_t, etc */ +#include "sds.h" /* for sds */ +#include "alloc.h" /* for allocation wrappers */ + +#define HIREDIS_MAJOR 0 +#define HIREDIS_MINOR 14 +#define HIREDIS_PATCH 1 +#define HIREDIS_SONAME 0.14 + +/* Connection type can be blocking or non-blocking and is set in the + * least significant bit of the flags field in redisContext. */ +#define REDIS_BLOCK 0x1 + +/* Connection may be disconnected before being free'd. The second bit + * in the flags field is set when the context is connected. */ +#define REDIS_CONNECTED 0x2 + +/* The async API might try to disconnect cleanly and flush the output + * buffer and read all subsequent replies before disconnecting. + * This flag means no new commands can come in and the connection + * should be terminated once all replies have been read. */ +#define REDIS_DISCONNECTING 0x4 + +/* Flag specific to the async API which means that the context should be clean + * up as soon as possible. */ +#define REDIS_FREEING 0x8 + +/* Flag that is set when an async callback is executed. */ +#define REDIS_IN_CALLBACK 0x10 + +/* Flag that is set when the async context has one or more subscriptions. */ +#define REDIS_SUBSCRIBED 0x20 + +/* Flag that is set when monitor mode is active */ +#define REDIS_MONITORING 0x40 + +/* Flag that is set when we should set SO_REUSEADDR before calling bind() */ +#define REDIS_REUSEADDR 0x80 + +#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ + +/* number of times we retry to connect in the case of EADDRNOTAVAIL and + * SO_REUSEADDR is being used. */ +#define REDIS_CONNECT_RETRIES 10 + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is the reply object returned by redisCommand() */ +typedef struct redisReply { + int type; /* REDIS_REPLY_* */ + long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ + size_t len; /* Length of string */ + char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ + size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ + struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ +} redisReply; + +redisReader *redisReaderCreate(void); + +/* Function to free the reply objects hiredis returns by default. */ +void freeReplyObject(void *reply); + +/* Functions to format a command according to the protocol. */ +int redisvFormatCommand(char **target, const char *format, va_list ap); +int redisFormatCommand(char **target, const char *format, ...); +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); +int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen); +void redisFreeCommand(char *cmd); +void redisFreeSdsCommand(sds cmd); + +enum redisConnectionType { + REDIS_CONN_TCP, + REDIS_CONN_UNIX +}; + +/* Context for a connection to Redis */ +typedef struct redisContext { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + int fd; + int flags; + char *obuf; /* Write buffer */ + redisReader *reader; /* Protocol reader */ + + enum redisConnectionType connection_type; + struct timeval *timeout; + + struct { + char *host; + char *source_addr; + int port; + } tcp; + + struct { + char *path; + } unix_sock; + +} redisContext; + +redisContext *redisConnect(const char *ip, int port); +redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); +redisContext *redisConnectNonBlock(const char *ip, int port); +redisContext *redisConnectBindNonBlock(const char *ip, int port, + const char *source_addr); +redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, + const char *source_addr); +redisContext *redisConnectUnix(const char *path); +redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); +redisContext *redisConnectUnixNonBlock(const char *path); +redisContext *redisConnectFd(int fd); + +/** + * Reconnect the given context using the saved information. + * + * This re-uses the exact same connect options as in the initial connection. + * host, ip (or path), timeout and bind address are reused, + * flags are used unmodified from the existing context. + * + * Returns REDIS_OK on successful connect or REDIS_ERR otherwise. + */ +int redisReconnect(redisContext *c); + +int redisSetTimeout(redisContext *c, const struct timeval tv); +int redisEnableKeepAlive(redisContext *c); +void redisFree(redisContext *c); +int redisFreeKeepFd(redisContext *c); +int redisBufferRead(redisContext *c); +int redisBufferWrite(redisContext *c, int *done); + +/* In a blocking context, this function first checks if there are unconsumed + * replies to return and returns one if so. Otherwise, it flushes the output + * buffer to the socket and reads until it has a reply. In a non-blocking + * context, it will return unconsumed replies until there are no more. */ +int redisGetReply(redisContext *c, void **reply); +int redisGetReplyFromReader(redisContext *c, void **reply); + +/* Write a formatted command to the output buffer. Use these functions in blocking mode + * to get a pipeline of commands. */ +int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len); + +/* Write a command to the output buffer. Use these functions in blocking mode + * to get a pipeline of commands. */ +int redisvAppendCommand(redisContext *c, const char *format, va_list ap); +int redisAppendCommand(redisContext *c, const char *format, ...); +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); + +/* Issue a command to Redis. In a blocking context, it is identical to calling + * redisAppendCommand, followed by redisGetReply. The function will return + * NULL if there was an error in performing the request, otherwise it will + * return the reply. In a non-blocking context, it is identical to calling + * only redisAppendCommand and will always return NULL. */ +void *redisvCommand(redisContext *c, const char *format, va_list ap); +void *redisCommand(redisContext *c, const char *format, ...); +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-0.14.1/net.c b/ext/hiredis-0.14.1/net.c new file mode 100644 index 000000000..d71bbcd57 --- /dev/null +++ b/ext/hiredis-0.14.1/net.c @@ -0,0 +1,477 @@ +/* Extracted from anet.c to work properly with Hiredis error reporting. + * + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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 "fmacros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "net.h" +#include "sds.h" + +/* Defined in hiredis.c */ +void __redisSetError(redisContext *c, int type, const char *str); + +static void redisContextCloseFd(redisContext *c) { + if (c && c->fd >= 0) { + close(c->fd); + c->fd = -1; + } +} + +static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { + int errorno = errno; /* snprintf() may change errno */ + char buf[128] = { 0 }; + size_t len = 0; + + if (prefix != NULL) + len = snprintf(buf,sizeof(buf),"%s: ",prefix); + strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len); + __redisSetError(c,type,buf); +} + +static int redisSetReuseAddr(redisContext *c) { + int on = 1; + if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +static int redisCreateSocket(redisContext *c, int type) { + int s; + if ((s = socket(type, SOCK_STREAM, 0)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + c->fd = s; + if (type == AF_INET) { + if (redisSetReuseAddr(c) == REDIS_ERR) { + return REDIS_ERR; + } + } + return REDIS_OK; +} + +static int redisSetBlocking(redisContext *c, int blocking) { + int flags; + + /* Set the socket nonblocking. + * Note that fcntl(2) for F_GETFL and F_SETFL can't be + * interrupted by a signal. */ + if ((flags = fcntl(c->fd, F_GETFL)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + + if (blocking) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if (fcntl(c->fd, F_SETFL, flags) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +int redisKeepAlive(redisContext *c, int interval) { + int val = 1; + int fd = c->fd; + + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = interval; + +#if defined(__APPLE__) && defined(__MACH__) + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#else +#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = interval/3; + if (val == 0) val = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = 3; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#endif +#endif + + return REDIS_OK; +} + +static int redisSetTcpNoDelay(redisContext *c) { + int yes = 1; + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) + +static int redisContextTimeoutMsec(redisContext *c, long *result) +{ + const struct timeval *timeout = c->timeout; + long msec = -1; + + /* Only use timeout when not NULL. */ + if (timeout != NULL) { + if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { + *result = msec; + return REDIS_ERR; + } + + msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); + + if (msec < 0 || msec > INT_MAX) { + msec = INT_MAX; + } + } + + *result = msec; + return REDIS_OK; +} + +static int redisContextWaitReady(redisContext *c, long msec) { + struct pollfd wfd[1]; + + wfd[0].fd = c->fd; + wfd[0].events = POLLOUT; + + if (errno == EINPROGRESS) { + int res; + + if ((res = poll(wfd, 1, msec)) == -1) { + __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); + redisContextCloseFd(c); + return REDIS_ERR; + } else if (res == 0) { + errno = ETIMEDOUT; + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; + } + + if (redisCheckSocketError(c) != REDIS_OK) + return REDIS_ERR; + + return REDIS_OK; + } + + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; +} + +int redisCheckSocketError(redisContext *c) { + int err = 0; + socklen_t errlen = sizeof(err); + + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); + return REDIS_ERR; + } + + if (err) { + errno = err; + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + + return REDIS_OK; +} + +int redisContextSetTimeout(redisContext *c, const struct timeval tv) { + if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); + return REDIS_ERR; + } + if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); + return REDIS_ERR; + } + return REDIS_OK; +} + +static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr) { + int s, rv, n; + char _port[6]; /* strlen("65535"); */ + struct addrinfo hints, *servinfo, *bservinfo, *p, *b; + int blocking = (c->flags & REDIS_BLOCK); + int reuseaddr = (c->flags & REDIS_REUSEADDR); + int reuses = 0; + long timeout_msec = -1; + + servinfo = NULL; + c->connection_type = REDIS_CONN_TCP; + c->tcp.port = port; + + /* We need to take possession of the passed parameters + * to make them reusable for a reconnect. + * We also carefully check we don't free data we already own, + * as in the case of the reconnect method. + * + * This is a bit ugly, but atleast it works and doesn't leak memory. + **/ + if (c->tcp.host != addr) { + free(c->tcp.host); + + c->tcp.host = hi_strdup(addr); + } + + if (timeout) { + if (c->timeout != timeout) { + if (c->timeout == NULL) + c->timeout = hi_malloc(sizeof(struct timeval)); + + memcpy(c->timeout, timeout, sizeof(struct timeval)); + } + } else { + free(c->timeout); + c->timeout = NULL; + } + + if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) { + __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified"); + goto error; + } + + if (source_addr == NULL) { + free(c->tcp.source_addr); + c->tcp.source_addr = NULL; + } else if (c->tcp.source_addr != source_addr) { + free(c->tcp.source_addr); + c->tcp.source_addr = hi_strdup(source_addr); + } + + snprintf(_port, 6, "%d", port); + memset(&hints,0,sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + /* Try with IPv6 if no IPv4 address was found. We do it in this order since + * in a Redis client you can't afford to test if you have IPv6 connectivity + * as this would add latency to every connect. Otherwise a more sensible + * route could be: Use IPv6 if both addresses are available and there is IPv6 + * connectivity. */ + if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) { + hints.ai_family = AF_INET6; + if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { + __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); + return REDIS_ERR; + } + } + for (p = servinfo; p != NULL; p = p->ai_next) { +addrretry: + if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) + continue; + + c->fd = s; + if (redisSetBlocking(c,0) != REDIS_OK) + goto error; + if (c->tcp.source_addr) { + int bound = 0; + /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ + if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + + if (reuseaddr) { + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, + sizeof(n)) < 0) { + freeaddrinfo(bservinfo); + goto error; + } + } + + for (b = bservinfo; b != NULL; b = b->ai_next) { + if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { + bound = 1; + break; + } + } + freeaddrinfo(bservinfo); + if (!bound) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + } + if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { + if (errno == EHOSTUNREACH) { + redisContextCloseFd(c); + continue; + } else if (errno == EINPROGRESS && !blocking) { + /* This is ok. */ + } else if (errno == EADDRNOTAVAIL && reuseaddr) { + if (++reuses >= REDIS_CONNECT_RETRIES) { + goto error; + } else { + redisContextCloseFd(c); + goto addrretry; + } + } else { + if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) + goto error; + } + } + if (blocking && redisSetBlocking(c,1) != REDIS_OK) + goto error; + if (redisSetTcpNoDelay(c) != REDIS_OK) + goto error; + + c->flags |= REDIS_CONNECTED; + rv = REDIS_OK; + goto end; + } + if (p == NULL) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + +error: + rv = REDIS_ERR; +end: + if(servinfo) { + freeaddrinfo(servinfo); + } + + return rv; // Need to return REDIS_OK if alright +} + +int redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout) { + return _redisContextConnectTcp(c, addr, port, timeout, NULL); +} + +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr) { + return _redisContextConnectTcp(c, addr, port, timeout, source_addr); +} + +int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { + int blocking = (c->flags & REDIS_BLOCK); + struct sockaddr_un sa; + long timeout_msec = -1; + + if (redisCreateSocket(c,AF_UNIX) < 0) + return REDIS_ERR; + if (redisSetBlocking(c,0) != REDIS_OK) + return REDIS_ERR; + + c->connection_type = REDIS_CONN_UNIX; + if (c->unix_sock.path != path) + c->unix_sock.path = hi_strdup(path); + + if (timeout) { + if (c->timeout != timeout) { + if (c->timeout == NULL) + c->timeout = hi_malloc(sizeof(struct timeval)); + + memcpy(c->timeout, timeout, sizeof(struct timeval)); + } + } else { + free(c->timeout); + c->timeout = NULL; + } + + if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) + return REDIS_ERR; + + sa.sun_family = AF_UNIX; + strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); + if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { + if (errno == EINPROGRESS && !blocking) { + /* This is ok. */ + } else { + if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) + return REDIS_ERR; + } + } + + /* Reset socket to be blocking after connect(2). */ + if (blocking && redisSetBlocking(c,1) != REDIS_OK) + return REDIS_ERR; + + c->flags |= REDIS_CONNECTED; + return REDIS_OK; +} diff --git a/ext/hiredis-0.14.1/net.h b/ext/hiredis-0.14.1/net.h new file mode 100644 index 000000000..d9dc36257 --- /dev/null +++ b/ext/hiredis-0.14.1/net.h @@ -0,0 +1,49 @@ +/* Extracted from anet.c to work properly with Hiredis error reporting. + * + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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. + */ + +#ifndef __NET_H +#define __NET_H + +#include "hiredis.h" + +int redisCheckSocketError(redisContext *c); +int redisContextSetTimeout(redisContext *c, const struct timeval tv); +int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr); +int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); +int redisKeepAlive(redisContext *c, int interval); + +#endif diff --git a/ext/hiredis-0.14.1/read.c b/ext/hiredis-0.14.1/read.c new file mode 100644 index 000000000..cc2126778 --- /dev/null +++ b/ext/hiredis-0.14.1/read.c @@ -0,0 +1,598 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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 "fmacros.h" +#include +#include +#ifndef _MSC_VER +#include +#endif +#include +#include +#include +#include + +#include "read.h" +#include "sds.h" + +static void __redisReaderSetError(redisReader *r, int type, const char *str) { + size_t len; + + if (r->reply != NULL && r->fn && r->fn->freeObject) { + r->fn->freeObject(r->reply); + r->reply = NULL; + } + + /* Clear input buffer on errors. */ + sdsfree(r->buf); + r->buf = NULL; + r->pos = r->len = 0; + + /* Reset task stack. */ + r->ridx = -1; + + /* Set error. */ + r->err = type; + len = strlen(str); + len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); + memcpy(r->errstr,str,len); + r->errstr[len] = '\0'; +} + +static size_t chrtos(char *buf, size_t size, char byte) { + size_t len = 0; + + switch(byte) { + case '\\': + case '"': + len = snprintf(buf,size,"\"\\%c\"",byte); + break; + case '\n': len = snprintf(buf,size,"\"\\n\""); break; + case '\r': len = snprintf(buf,size,"\"\\r\""); break; + case '\t': len = snprintf(buf,size,"\"\\t\""); break; + case '\a': len = snprintf(buf,size,"\"\\a\""); break; + case '\b': len = snprintf(buf,size,"\"\\b\""); break; + default: + if (isprint(byte)) + len = snprintf(buf,size,"\"%c\"",byte); + else + len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); + break; + } + + return len; +} + +static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) { + char cbuf[8], sbuf[128]; + + chrtos(cbuf,sizeof(cbuf),byte); + snprintf(sbuf,sizeof(sbuf), + "Protocol error, got %s as reply type byte", cbuf); + __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf); +} + +static void __redisReaderSetErrorOOM(redisReader *r) { + __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory"); +} + +static char *readBytes(redisReader *r, unsigned int bytes) { + char *p; + if (r->len-r->pos >= bytes) { + p = r->buf+r->pos; + r->pos += bytes; + return p; + } + return NULL; +} + +/* Find pointer to \r\n. */ +static char *seekNewline(char *s, size_t len) { + int pos = 0; + int _len = len-1; + + /* Position should be < len-1 because the character at "pos" should be + * followed by a \n. Note that strchr cannot be used because it doesn't + * allow to search a limited length and the buffer that is being searched + * might not have a trailing NULL character. */ + while (pos < _len) { + while(pos < _len && s[pos] != '\r') pos++; + if (pos==_len) { + /* Not found. */ + return NULL; + } else { + if (s[pos+1] == '\n') { + /* Found. */ + return s+pos; + } else { + /* Continue searching. */ + pos++; + } + } + } + return NULL; +} + +/* Convert a string into a long long. Returns REDIS_OK if the string could be + * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value + * will be set to the parsed value when appropriate. + * + * Note that this function demands that the string strictly represents + * a long long: no spaces or other characters before or after the string + * representing the number are accepted, nor zeroes at the start if not + * for the string "0" representing the zero number. + * + * Because of its strictness, it is safe to use this function to check if + * you can convert a string into a long long, and obtain back the string + * from the number without any loss in the string representation. */ +static int string2ll(const char *s, size_t slen, long long *value) { + const char *p = s; + size_t plen = 0; + int negative = 0; + unsigned long long v; + + if (plen == slen) + return REDIS_ERR; + + /* Special case: first and only digit is 0. */ + if (slen == 1 && p[0] == '0') { + if (value != NULL) *value = 0; + return REDIS_OK; + } + + if (p[0] == '-') { + negative = 1; + p++; plen++; + + /* Abort on only a negative sign. */ + if (plen == slen) + return REDIS_ERR; + } + + /* First digit should be 1-9, otherwise the string should just be 0. */ + if (p[0] >= '1' && p[0] <= '9') { + v = p[0]-'0'; + p++; plen++; + } else if (p[0] == '0' && slen == 1) { + *value = 0; + return REDIS_OK; + } else { + return REDIS_ERR; + } + + while (plen < slen && p[0] >= '0' && p[0] <= '9') { + if (v > (ULLONG_MAX / 10)) /* Overflow. */ + return REDIS_ERR; + v *= 10; + + if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */ + return REDIS_ERR; + v += p[0]-'0'; + + p++; plen++; + } + + /* Return if not all bytes were used. */ + if (plen < slen) + return REDIS_ERR; + + if (negative) { + if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */ + return REDIS_ERR; + if (value != NULL) *value = -v; + } else { + if (v > LLONG_MAX) /* Overflow. */ + return REDIS_ERR; + if (value != NULL) *value = v; + } + return REDIS_OK; +} + +static char *readLine(redisReader *r, int *_len) { + char *p, *s; + int len; + + p = r->buf+r->pos; + s = seekNewline(p,(r->len-r->pos)); + if (s != NULL) { + len = s-(r->buf+r->pos); + r->pos += len+2; /* skip \r\n */ + if (_len) *_len = len; + return p; + } + return NULL; +} + +static void moveToNextTask(redisReader *r) { + redisReadTask *cur, *prv; + while (r->ridx >= 0) { + /* Return a.s.a.p. when the stack is now empty. */ + if (r->ridx == 0) { + r->ridx--; + return; + } + + cur = &(r->rstack[r->ridx]); + prv = &(r->rstack[r->ridx-1]); + assert(prv->type == REDIS_REPLY_ARRAY); + if (cur->idx == prv->elements-1) { + r->ridx--; + } else { + /* Reset the type because the next item can be anything */ + assert(cur->idx < prv->elements); + cur->type = -1; + cur->elements = -1; + cur->idx++; + return; + } + } +} + +static int processLineItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj; + char *p; + int len; + + if ((p = readLine(r,&len)) != NULL) { + if (cur->type == REDIS_REPLY_INTEGER) { + if (r->fn && r->fn->createInteger) { + long long v; + if (string2ll(p, len, &v) == REDIS_ERR) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad integer value"); + return REDIS_ERR; + } + obj = r->fn->createInteger(cur,v); + } else { + obj = (void*)REDIS_REPLY_INTEGER; + } + } else { + /* Type will be error or status. */ + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,p,len); + else + obj = (void*)(size_t)(cur->type); + } + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + /* Set reply if this is the root object. */ + if (r->ridx == 0) r->reply = obj; + moveToNextTask(r); + return REDIS_OK; + } + + return REDIS_ERR; +} + +static int processBulkItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj = NULL; + char *p, *s; + long long len; + unsigned long bytelen; + int success = 0; + + p = r->buf+r->pos; + s = seekNewline(p,r->len-r->pos); + if (s != NULL) { + p = r->buf+r->pos; + bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ + + if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad bulk string length"); + return REDIS_ERR; + } + + if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bulk string length out of range"); + return REDIS_ERR; + } + + if (len == -1) { + /* The nil object can always be created. */ + if (r->fn && r->fn->createNil) + obj = r->fn->createNil(cur); + else + obj = (void*)REDIS_REPLY_NIL; + success = 1; + } else { + /* Only continue when the buffer contains the entire bulk item. */ + bytelen += len+2; /* include \r\n */ + if (r->pos+bytelen <= r->len) { + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,s+2,len); + else + obj = (void*)REDIS_REPLY_STRING; + success = 1; + } + } + + /* Proceed when obj was created. */ + if (success) { + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + r->pos += bytelen; + + /* Set reply if this is the root object. */ + if (r->ridx == 0) r->reply = obj; + moveToNextTask(r); + return REDIS_OK; + } + } + + return REDIS_ERR; +} + +static int processMultiBulkItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj; + char *p; + long long elements; + int root = 0, len; + + /* Set error for nested multi bulks with depth > 7 */ + if (r->ridx == 8) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "No support for nested multi bulk replies with depth > 7"); + return REDIS_ERR; + } + + if ((p = readLine(r,&len)) != NULL) { + if (string2ll(p, len, &elements) == REDIS_ERR) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad multi-bulk length"); + return REDIS_ERR; + } + + root = (r->ridx == 0); + + if (elements < -1 || elements > INT_MAX) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Multi-bulk length out of range"); + return REDIS_ERR; + } + + if (elements == -1) { + if (r->fn && r->fn->createNil) + obj = r->fn->createNil(cur); + else + obj = (void*)REDIS_REPLY_NIL; + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + moveToNextTask(r); + } else { + if (r->fn && r->fn->createArray) + obj = r->fn->createArray(cur,elements); + else + obj = (void*)REDIS_REPLY_ARRAY; + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + /* Modify task stack when there are more than 0 elements. */ + if (elements > 0) { + cur->elements = elements; + cur->obj = obj; + r->ridx++; + r->rstack[r->ridx].type = -1; + r->rstack[r->ridx].elements = -1; + r->rstack[r->ridx].idx = 0; + r->rstack[r->ridx].obj = NULL; + r->rstack[r->ridx].parent = cur; + r->rstack[r->ridx].privdata = r->privdata; + } else { + moveToNextTask(r); + } + } + + /* Set reply if this is the root object. */ + if (root) r->reply = obj; + return REDIS_OK; + } + + return REDIS_ERR; +} + +static int processItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + char *p; + + /* check if we need to read type */ + if (cur->type < 0) { + if ((p = readBytes(r,1)) != NULL) { + switch (p[0]) { + case '-': + cur->type = REDIS_REPLY_ERROR; + break; + case '+': + cur->type = REDIS_REPLY_STATUS; + break; + case ':': + cur->type = REDIS_REPLY_INTEGER; + break; + case '$': + cur->type = REDIS_REPLY_STRING; + break; + case '*': + cur->type = REDIS_REPLY_ARRAY; + break; + default: + __redisReaderSetErrorProtocolByte(r,*p); + return REDIS_ERR; + } + } else { + /* could not consume 1 byte */ + return REDIS_ERR; + } + } + + /* process typed item */ + switch(cur->type) { + case REDIS_REPLY_ERROR: + case REDIS_REPLY_STATUS: + case REDIS_REPLY_INTEGER: + return processLineItem(r); + case REDIS_REPLY_STRING: + return processBulkItem(r); + case REDIS_REPLY_ARRAY: + return processMultiBulkItem(r); + default: + assert(NULL); + return REDIS_ERR; /* Avoid warning. */ + } +} + +redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { + redisReader *r; + + r = calloc(1,sizeof(redisReader)); + if (r == NULL) + return NULL; + + r->fn = fn; + r->buf = sdsempty(); + r->maxbuf = REDIS_READER_MAX_BUF; + if (r->buf == NULL) { + free(r); + return NULL; + } + + r->ridx = -1; + return r; +} + +void redisReaderFree(redisReader *r) { + if (r == NULL) + return; + if (r->reply != NULL && r->fn && r->fn->freeObject) + r->fn->freeObject(r->reply); + sdsfree(r->buf); + free(r); +} + +int redisReaderFeed(redisReader *r, const char *buf, size_t len) { + sds newbuf; + + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; + + /* Copy the provided buffer. */ + if (buf != NULL && len >= 1) { + /* Destroy internal buffer when it is empty and is quite large. */ + if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { + sdsfree(r->buf); + r->buf = sdsempty(); + r->pos = 0; + + /* r->buf should not be NULL since we just free'd a larger one. */ + assert(r->buf != NULL); + } + + newbuf = sdscatlen(r->buf,buf,len); + if (newbuf == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + r->buf = newbuf; + r->len = sdslen(r->buf); + } + + return REDIS_OK; +} + +int redisReaderGetReply(redisReader *r, void **reply) { + /* Default target pointer to NULL. */ + if (reply != NULL) + *reply = NULL; + + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; + + /* When the buffer is empty, there will never be a reply. */ + if (r->len == 0) + return REDIS_OK; + + /* Set first item to process when the stack is empty. */ + if (r->ridx == -1) { + r->rstack[0].type = -1; + r->rstack[0].elements = -1; + r->rstack[0].idx = -1; + r->rstack[0].obj = NULL; + r->rstack[0].parent = NULL; + r->rstack[0].privdata = r->privdata; + r->ridx = 0; + } + + /* Process items in reply. */ + while (r->ridx >= 0) + if (processItem(r) != REDIS_OK) + break; + + /* Return ASAP when an error occurred. */ + if (r->err) + return REDIS_ERR; + + /* Discard part of the buffer when we've consumed at least 1k, to avoid + * doing unnecessary calls to memmove() in sds.c. */ + if (r->pos >= 1024) { + sdsrange(r->buf,r->pos,-1); + r->pos = 0; + r->len = sdslen(r->buf); + } + + /* Emit a reply when there is one. */ + if (r->ridx == -1) { + if (reply != NULL) + *reply = r->reply; + r->reply = NULL; + } + return REDIS_OK; +} diff --git a/ext/hiredis-0.14.1/read.h b/ext/hiredis-0.14.1/read.h new file mode 100644 index 000000000..2988aa453 --- /dev/null +++ b/ext/hiredis-0.14.1/read.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + + +#ifndef __HIREDIS_READ_H +#define __HIREDIS_READ_H +#include /* for size_t */ + +#define REDIS_ERR -1 +#define REDIS_OK 0 + +/* When an error occurs, the err flag in a context is set to hold the type of + * error that occurred. REDIS_ERR_IO means there was an I/O error and you + * should use the "errno" variable to find out what is wrong. + * For other values, the "errstr" field will hold a description. */ +#define REDIS_ERR_IO 1 /* Error in read or write */ +#define REDIS_ERR_EOF 3 /* End of file */ +#define REDIS_ERR_PROTOCOL 4 /* Protocol error */ +#define REDIS_ERR_OOM 5 /* Out of memory */ +#define REDIS_ERR_OTHER 2 /* Everything else... */ + +#define REDIS_REPLY_STRING 1 +#define REDIS_REPLY_ARRAY 2 +#define REDIS_REPLY_INTEGER 3 +#define REDIS_REPLY_NIL 4 +#define REDIS_REPLY_STATUS 5 +#define REDIS_REPLY_ERROR 6 + +#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct redisReadTask { + int type; + int elements; /* number of elements in multibulk container */ + int idx; /* index in parent (array) object */ + void *obj; /* holds user-generated value for a read task */ + struct redisReadTask *parent; /* parent task */ + void *privdata; /* user-settable arbitrary field */ +} redisReadTask; + +typedef struct redisReplyObjectFunctions { + void *(*createString)(const redisReadTask*, char*, size_t); + void *(*createArray)(const redisReadTask*, int); + void *(*createInteger)(const redisReadTask*, long long); + void *(*createNil)(const redisReadTask*); + void (*freeObject)(void*); +} redisReplyObjectFunctions; + +typedef struct redisReader { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + + char *buf; /* Read buffer */ + size_t pos; /* Buffer cursor */ + size_t len; /* Buffer length */ + size_t maxbuf; /* Max length of unused buffer */ + + redisReadTask rstack[9]; + int ridx; /* Index of current read task */ + void *reply; /* Temporary reply pointer */ + + redisReplyObjectFunctions *fn; + void *privdata; +} redisReader; + +/* Public API for the protocol parser. */ +redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn); +void redisReaderFree(redisReader *r); +int redisReaderFeed(redisReader *r, const char *buf, size_t len); +int redisReaderGetReply(redisReader *r, void **reply); + +#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) +#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply) +#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-0.14.1/sds.c b/ext/hiredis-0.14.1/sds.c new file mode 100644 index 000000000..923ffd82f --- /dev/null +++ b/ext/hiredis-0.14.1/sds.c @@ -0,0 +1,1272 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, Redis Labs, Inc + * 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 +#include +#include +#include +#include +#include "sds.h" +#include "sdsalloc.h" + +static inline int sdsHdrSize(char type) { + switch(type&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return sizeof(struct sdshdr5); + case SDS_TYPE_8: + return sizeof(struct sdshdr8); + case SDS_TYPE_16: + return sizeof(struct sdshdr16); + case SDS_TYPE_32: + return sizeof(struct sdshdr32); + case SDS_TYPE_64: + return sizeof(struct sdshdr64); + } + return 0; +} + +static inline char sdsReqType(size_t string_size) { + if (string_size < 32) + return SDS_TYPE_5; + if (string_size < 0xff) + return SDS_TYPE_8; + if (string_size < 0xffff) + return SDS_TYPE_16; + if (string_size < 0xffffffff) + return SDS_TYPE_32; + return SDS_TYPE_64; +} + +/* Create a new sds string with the content specified by the 'init' pointer + * and 'initlen'. + * If NULL is used for 'init' the string is initialized with zero bytes. + * + * The string is always null-termined (all the sds strings are, always) so + * even if you create an sds string with: + * + * mystring = sdsnewlen("abc",3); + * + * You can print the string with printf() as there is an implicit \0 at the + * end of the string. However the string is binary safe and can contain + * \0 characters in the middle, as the length is stored in the sds header. */ +sds sdsnewlen(const void *init, size_t initlen) { + void *sh; + sds s; + char type = sdsReqType(initlen); + /* Empty strings are usually created in order to append. Use type 8 + * since type 5 is not good at this. */ + if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; + int hdrlen = sdsHdrSize(type); + unsigned char *fp; /* flags pointer. */ + + sh = s_malloc(hdrlen+initlen+1); + if (sh == NULL) return NULL; + if (!init) + memset(sh, 0, hdrlen+initlen+1); + s = (char*)sh+hdrlen; + fp = ((unsigned char*)s)-1; + switch(type) { + case SDS_TYPE_5: { + *fp = type | (initlen << SDS_TYPE_BITS); + break; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + } + if (initlen && init) + memcpy(s, init, initlen); + s[initlen] = '\0'; + return s; +} + +/* Create an empty (zero length) sds string. Even in this case the string + * always has an implicit null term. */ +sds sdsempty(void) { + return sdsnewlen("",0); +} + +/* Create a new sds string starting from a null terminated C string. */ +sds sdsnew(const char *init) { + size_t initlen = (init == NULL) ? 0 : strlen(init); + return sdsnewlen(init, initlen); +} + +/* Duplicate an sds string. */ +sds sdsdup(const sds s) { + return sdsnewlen(s, sdslen(s)); +} + +/* Free an sds string. No operation is performed if 's' is NULL. */ +void sdsfree(sds s) { + if (s == NULL) return; + s_free((char*)s-sdsHdrSize(s[-1])); +} + +/* Set the sds string length to the length as obtained with strlen(), so + * considering as content only up to the first null term character. + * + * This function is useful when the sds string is hacked manually in some + * way, like in the following example: + * + * s = sdsnew("foobar"); + * s[2] = '\0'; + * sdsupdatelen(s); + * printf("%d\n", sdslen(s)); + * + * The output will be "2", but if we comment out the call to sdsupdatelen() + * the output will be "6" as the string was modified but the logical length + * remains 6 bytes. */ +void sdsupdatelen(sds s) { + int reallen = strlen(s); + sdssetlen(s, reallen); +} + +/* Modify an sds string in-place to make it empty (zero length). + * However all the existing buffer is not discarded but set as free space + * so that next append operations will not require allocations up to the + * number of bytes previously available. */ +void sdsclear(sds s) { + sdssetlen(s, 0); + s[0] = '\0'; +} + +/* Enlarge the free space at the end of the sds string so that the caller + * is sure that after calling this function can overwrite up to addlen + * bytes after the end of the string, plus one more byte for nul term. + * + * Note: this does not change the *length* of the sds string as returned + * by sdslen(), but only the free buffer space we have. */ +sds sdsMakeRoomFor(sds s, size_t addlen) { + void *sh, *newsh; + size_t avail = sdsavail(s); + size_t len, newlen; + char type, oldtype = s[-1] & SDS_TYPE_MASK; + int hdrlen; + + /* Return ASAP if there is enough space left. */ + if (avail >= addlen) return s; + + len = sdslen(s); + sh = (char*)s-sdsHdrSize(oldtype); + newlen = (len+addlen); + if (newlen < SDS_MAX_PREALLOC) + newlen *= 2; + else + newlen += SDS_MAX_PREALLOC; + + type = sdsReqType(newlen); + + /* Don't use type 5: the user is appending to the string and type 5 is + * not able to remember empty space, so sdsMakeRoomFor() must be called + * at every appending operation. */ + if (type == SDS_TYPE_5) type = SDS_TYPE_8; + + hdrlen = sdsHdrSize(type); + if (oldtype==type) { + newsh = s_realloc(sh, hdrlen+newlen+1); + if (newsh == NULL) return NULL; + s = (char*)newsh+hdrlen; + } else { + /* Since the header size changes, need to move the string forward, + * and can't use realloc */ + newsh = s_malloc(hdrlen+newlen+1); + if (newsh == NULL) return NULL; + memcpy((char*)newsh+hdrlen, s, len+1); + s_free(sh); + s = (char*)newsh+hdrlen; + s[-1] = type; + sdssetlen(s, len); + } + sdssetalloc(s, newlen); + return s; +} + +/* Reallocate the sds string so that it has no free space at the end. The + * contained string remains not altered, but next concatenation operations + * will require a reallocation. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdsRemoveFreeSpace(sds s) { + void *sh, *newsh; + char type, oldtype = s[-1] & SDS_TYPE_MASK; + int hdrlen; + size_t len = sdslen(s); + sh = (char*)s-sdsHdrSize(oldtype); + + type = sdsReqType(len); + hdrlen = sdsHdrSize(type); + if (oldtype==type) { + newsh = s_realloc(sh, hdrlen+len+1); + if (newsh == NULL) return NULL; + s = (char*)newsh+hdrlen; + } else { + newsh = s_malloc(hdrlen+len+1); + if (newsh == NULL) return NULL; + memcpy((char*)newsh+hdrlen, s, len+1); + s_free(sh); + s = (char*)newsh+hdrlen; + s[-1] = type; + sdssetlen(s, len); + } + sdssetalloc(s, len); + return s; +} + +/* Return the total size of the allocation of the specifed sds string, + * including: + * 1) The sds header before the pointer. + * 2) The string. + * 3) The free buffer at the end if any. + * 4) The implicit null term. + */ +size_t sdsAllocSize(sds s) { + size_t alloc = sdsalloc(s); + return sdsHdrSize(s[-1])+alloc+1; +} + +/* Return the pointer of the actual SDS allocation (normally SDS strings + * are referenced by the start of the string buffer). */ +void *sdsAllocPtr(sds s) { + return (void*) (s-sdsHdrSize(s[-1])); +} + +/* Increment the sds length and decrements the left free space at the + * end of the string according to 'incr'. Also set the null term + * in the new end of the string. + * + * This function is used in order to fix the string length after the + * user calls sdsMakeRoomFor(), writes something after the end of + * the current string, and finally needs to set the new length. + * + * Note: it is possible to use a negative increment in order to + * right-trim the string. + * + * Usage example: + * + * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the + * following schema, to cat bytes coming from the kernel to the end of an + * sds string without copying into an intermediate buffer: + * + * oldlen = sdslen(s); + * s = sdsMakeRoomFor(s, BUFFER_SIZE); + * nread = read(fd, s+oldlen, BUFFER_SIZE); + * ... check for nread <= 0 and handle it ... + * sdsIncrLen(s, nread); + */ +void sdsIncrLen(sds s, int incr) { + unsigned char flags = s[-1]; + size_t len; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { + unsigned char *fp = ((unsigned char*)s)-1; + unsigned char oldlen = SDS_TYPE_5_LEN(flags); + assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); + *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS); + len = oldlen+incr; + break; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); + len = (sh->len += incr); + break; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); + len = (sh->len += incr); + break; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); + len = (sh->len += incr); + break; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr))); + len = (sh->len += incr); + break; + } + default: len = 0; /* Just to avoid compilation warnings. */ + } + s[len] = '\0'; +} + +/* Grow the sds to have the specified length. Bytes that were not part of + * the original length of the sds will be set to zero. + * + * if the specified length is smaller than the current length, no operation + * is performed. */ +sds sdsgrowzero(sds s, size_t len) { + size_t curlen = sdslen(s); + + if (len <= curlen) return s; + s = sdsMakeRoomFor(s,len-curlen); + if (s == NULL) return NULL; + + /* Make sure added region doesn't contain garbage */ + memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ + sdssetlen(s, len); + return s; +} + +/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the + * end of the specified sds string 's'. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatlen(sds s, const void *t, size_t len) { + size_t curlen = sdslen(s); + + s = sdsMakeRoomFor(s,len); + if (s == NULL) return NULL; + memcpy(s+curlen, t, len); + sdssetlen(s, curlen+len); + s[curlen+len] = '\0'; + return s; +} + +/* Append the specified null termianted C string to the sds string 's'. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscat(sds s, const char *t) { + return sdscatlen(s, t, strlen(t)); +} + +/* Append the specified sds 't' to the existing sds 's'. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatsds(sds s, const sds t) { + return sdscatlen(s, t, sdslen(t)); +} + +/* Destructively modify the sds string 's' to hold the specified binary + * safe string pointed by 't' of length 'len' bytes. */ +sds sdscpylen(sds s, const char *t, size_t len) { + if (sdsalloc(s) < len) { + s = sdsMakeRoomFor(s,len-sdslen(s)); + if (s == NULL) return NULL; + } + memcpy(s, t, len); + s[len] = '\0'; + sdssetlen(s, len); + return s; +} + +/* Like sdscpylen() but 't' must be a null-termined string so that the length + * of the string is obtained with strlen(). */ +sds sdscpy(sds s, const char *t) { + return sdscpylen(s, t, strlen(t)); +} + +/* Helper for sdscatlonglong() doing the actual number -> string + * conversion. 's' must point to a string with room for at least + * SDS_LLSTR_SIZE bytes. + * + * The function returns the length of the null-terminated string + * representation stored at 's'. */ +#define SDS_LLSTR_SIZE 21 +int sdsll2str(char *s, long long value) { + char *p, aux; + unsigned long long v; + size_t l; + + /* Generate the string representation, this method produces + * an reversed string. */ + v = (value < 0) ? -value : value; + p = s; + do { + *p++ = '0'+(v%10); + v /= 10; + } while(v); + if (value < 0) *p++ = '-'; + + /* Compute length and add null term. */ + l = p-s; + *p = '\0'; + + /* Reverse the string. */ + p--; + while(s < p) { + aux = *s; + *s = *p; + *p = aux; + s++; + p--; + } + return l; +} + +/* Identical sdsll2str(), but for unsigned long long type. */ +int sdsull2str(char *s, unsigned long long v) { + char *p, aux; + size_t l; + + /* Generate the string representation, this method produces + * an reversed string. */ + p = s; + do { + *p++ = '0'+(v%10); + v /= 10; + } while(v); + + /* Compute length and add null term. */ + l = p-s; + *p = '\0'; + + /* Reverse the string. */ + p--; + while(s < p) { + aux = *s; + *s = *p; + *p = aux; + s++; + p--; + } + return l; +} + +/* Create an sds string from a long long value. It is much faster than: + * + * sdscatprintf(sdsempty(),"%lld\n", value); + */ +sds sdsfromlonglong(long long value) { + char buf[SDS_LLSTR_SIZE]; + int len = sdsll2str(buf,value); + + return sdsnewlen(buf,len); +} + +/* Like sdscatprintf() but gets va_list instead of being variadic. */ +sds sdscatvprintf(sds s, const char *fmt, va_list ap) { + va_list cpy; + char staticbuf[1024], *buf = staticbuf, *t; + size_t buflen = strlen(fmt)*2; + + /* We try to start using a static buffer for speed. + * If not possible we revert to heap allocation. */ + if (buflen > sizeof(staticbuf)) { + buf = s_malloc(buflen); + if (buf == NULL) return NULL; + } else { + buflen = sizeof(staticbuf); + } + + /* Try with buffers two times bigger every time we fail to + * fit the string in the current buffer size. */ + while(1) { + buf[buflen-2] = '\0'; + va_copy(cpy,ap); + vsnprintf(buf, buflen, fmt, cpy); + va_end(cpy); + if (buf[buflen-2] != '\0') { + if (buf != staticbuf) s_free(buf); + buflen *= 2; + buf = s_malloc(buflen); + if (buf == NULL) return NULL; + continue; + } + break; + } + + /* Finally concat the obtained string to the SDS string and return it. */ + t = sdscat(s, buf); + if (buf != staticbuf) s_free(buf); + return t; +} + +/* Append to the sds string 's' a string obtained using printf-alike format + * specifier. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. + * + * Example: + * + * s = sdsnew("Sum is: "); + * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b). + * + * Often you need to create a string from scratch with the printf-alike + * format. When this is the need, just use sdsempty() as the target string: + * + * s = sdscatprintf(sdsempty(), "... your format ...", args); + */ +sds sdscatprintf(sds s, const char *fmt, ...) { + va_list ap; + char *t; + va_start(ap, fmt); + t = sdscatvprintf(s,fmt,ap); + va_end(ap); + return t; +} + +/* This function is similar to sdscatprintf, but much faster as it does + * not rely on sprintf() family functions implemented by the libc that + * are often very slow. Moreover directly handling the sds string as + * new data is concatenated provides a performance improvement. + * + * However this function only handles an incompatible subset of printf-alike + * format specifiers: + * + * %s - C String + * %S - SDS string + * %i - signed int + * %I - 64 bit signed integer (long long, int64_t) + * %u - unsigned int + * %U - 64 bit unsigned integer (unsigned long long, uint64_t) + * %% - Verbatim "%" character. + */ +sds sdscatfmt(sds s, char const *fmt, ...) { + const char *f = fmt; + int i; + va_list ap; + + va_start(ap,fmt); + i = sdslen(s); /* Position of the next byte to write to dest str. */ + while(*f) { + char next, *str; + size_t l; + long long num; + unsigned long long unum; + + /* Make sure there is always space for at least 1 char. */ + if (sdsavail(s)==0) { + s = sdsMakeRoomFor(s,1); + } + + switch(*f) { + case '%': + next = *(f+1); + f++; + switch(next) { + case 's': + case 'S': + str = va_arg(ap,char*); + l = (next == 's') ? strlen(str) : sdslen(str); + if (sdsavail(s) < l) { + s = sdsMakeRoomFor(s,l); + } + memcpy(s+i,str,l); + sdsinclen(s,l); + i += l; + break; + case 'i': + case 'I': + if (next == 'i') + num = va_arg(ap,int); + else + num = va_arg(ap,long long); + { + char buf[SDS_LLSTR_SIZE]; + l = sdsll2str(buf,num); + if (sdsavail(s) < l) { + s = sdsMakeRoomFor(s,l); + } + memcpy(s+i,buf,l); + sdsinclen(s,l); + i += l; + } + break; + case 'u': + case 'U': + if (next == 'u') + unum = va_arg(ap,unsigned int); + else + unum = va_arg(ap,unsigned long long); + { + char buf[SDS_LLSTR_SIZE]; + l = sdsull2str(buf,unum); + if (sdsavail(s) < l) { + s = sdsMakeRoomFor(s,l); + } + memcpy(s+i,buf,l); + sdsinclen(s,l); + i += l; + } + break; + default: /* Handle %% and generally %. */ + s[i++] = next; + sdsinclen(s,1); + break; + } + break; + default: + s[i++] = *f; + sdsinclen(s,1); + break; + } + f++; + } + va_end(ap); + + /* Add null-term */ + s[i] = '\0'; + return s; +} + +/* Remove the part of the string from left and from right composed just of + * contiguous characters found in 'cset', that is a null terminted C string. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. + * + * Example: + * + * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); + * s = sdstrim(s,"Aa. :"); + * printf("%s\n", s); + * + * Output will be just "Hello World". + */ +sds sdstrim(sds s, const char *cset) { + char *start, *end, *sp, *ep; + size_t len; + + sp = start = s; + ep = end = s+sdslen(s)-1; + while(sp <= end && strchr(cset, *sp)) sp++; + while(ep > sp && strchr(cset, *ep)) ep--; + len = (sp > ep) ? 0 : ((ep-sp)+1); + if (s != sp) memmove(s, sp, len); + s[len] = '\0'; + sdssetlen(s,len); + return s; +} + +/* Turn the string into a smaller (or equal) string containing only the + * substring specified by the 'start' and 'end' indexes. + * + * start and end can be negative, where -1 means the last character of the + * string, -2 the penultimate character, and so forth. + * + * The interval is inclusive, so the start and end characters will be part + * of the resulting string. + * + * The string is modified in-place. + * + * Example: + * + * s = sdsnew("Hello World"); + * sdsrange(s,1,-1); => "ello World" + */ +void sdsrange(sds s, int start, int end) { + size_t newlen, len = sdslen(s); + + if (len == 0) return; + if (start < 0) { + start = len+start; + if (start < 0) start = 0; + } + if (end < 0) { + end = len+end; + if (end < 0) end = 0; + } + newlen = (start > end) ? 0 : (end-start)+1; + if (newlen != 0) { + if (start >= (signed)len) { + newlen = 0; + } else if (end >= (signed)len) { + end = len-1; + newlen = (start > end) ? 0 : (end-start)+1; + } + } else { + start = 0; + } + if (start && newlen) memmove(s, s+start, newlen); + s[newlen] = 0; + sdssetlen(s,newlen); +} + +/* Apply tolower() to every character of the sds string 's'. */ +void sdstolower(sds s) { + int len = sdslen(s), j; + + for (j = 0; j < len; j++) s[j] = tolower(s[j]); +} + +/* Apply toupper() to every character of the sds string 's'. */ +void sdstoupper(sds s) { + int len = sdslen(s), j; + + for (j = 0; j < len; j++) s[j] = toupper(s[j]); +} + +/* Compare two sds strings s1 and s2 with memcmp(). + * + * Return value: + * + * positive if s1 > s2. + * negative if s1 < s2. + * 0 if s1 and s2 are exactly the same binary string. + * + * If two strings share exactly the same prefix, but one of the two has + * additional characters, the longer string is considered to be greater than + * the smaller one. */ +int sdscmp(const sds s1, const sds s2) { + size_t l1, l2, minlen; + int cmp; + + l1 = sdslen(s1); + l2 = sdslen(s2); + minlen = (l1 < l2) ? l1 : l2; + cmp = memcmp(s1,s2,minlen); + if (cmp == 0) return l1-l2; + return cmp; +} + +/* Split 's' with separator in 'sep'. An array + * of sds strings is returned. *count will be set + * by reference to the number of tokens returned. + * + * On out of memory, zero length string, zero length + * separator, NULL is returned. + * + * Note that 'sep' is able to split a string using + * a multi-character separator. For example + * sdssplit("foo_-_bar","_-_"); will return two + * elements "foo" and "bar". + * + * This version of the function is binary-safe but + * requires length arguments. sdssplit() is just the + * same function but for zero-terminated strings. + */ +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { + int elements = 0, slots = 5, start = 0, j; + sds *tokens; + + if (seplen < 1 || len < 0) return NULL; + + tokens = s_malloc(sizeof(sds)*slots); + if (tokens == NULL) return NULL; + + if (len == 0) { + *count = 0; + return tokens; + } + for (j = 0; j < (len-(seplen-1)); j++) { + /* make sure there is room for the next element and the final one */ + if (slots < elements+2) { + sds *newtokens; + + slots *= 2; + newtokens = s_realloc(tokens,sizeof(sds)*slots); + if (newtokens == NULL) goto cleanup; + tokens = newtokens; + } + /* search the separator */ + if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { + tokens[elements] = sdsnewlen(s+start,j-start); + if (tokens[elements] == NULL) goto cleanup; + elements++; + start = j+seplen; + j = j+seplen-1; /* skip the separator */ + } + } + /* Add the final element. We are sure there is room in the tokens array. */ + tokens[elements] = sdsnewlen(s+start,len-start); + if (tokens[elements] == NULL) goto cleanup; + elements++; + *count = elements; + return tokens; + +cleanup: + { + int i; + for (i = 0; i < elements; i++) sdsfree(tokens[i]); + s_free(tokens); + *count = 0; + return NULL; + } +} + +/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ +void sdsfreesplitres(sds *tokens, int count) { + if (!tokens) return; + while(count--) + sdsfree(tokens[count]); + s_free(tokens); +} + +/* Append to the sds string "s" an escaped string representation where + * all the non-printable characters (tested with isprint()) are turned into + * escapes in the form "\n\r\a...." or "\x". + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatrepr(sds s, const char *p, size_t len) { + s = sdscatlen(s,"\"",1); + while(len--) { + switch(*p) { + case '\\': + case '"': + s = sdscatprintf(s,"\\%c",*p); + break; + case '\n': s = sdscatlen(s,"\\n",2); break; + case '\r': s = sdscatlen(s,"\\r",2); break; + case '\t': s = sdscatlen(s,"\\t",2); break; + case '\a': s = sdscatlen(s,"\\a",2); break; + case '\b': s = sdscatlen(s,"\\b",2); break; + default: + if (isprint(*p)) + s = sdscatprintf(s,"%c",*p); + else + s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); + break; + } + p++; + } + return sdscatlen(s,"\"",1); +} + +/* Helper function for sdssplitargs() that returns non zero if 'c' + * is a valid hex digit. */ +int is_hex_digit(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); +} + +/* Helper function for sdssplitargs() that converts a hex digit into an + * integer from 0 to 15 */ +int hex_digit_to_int(char c) { + switch(c) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + default: return 0; + } +} + +/* Split a line into arguments, where every argument can be in the + * following programming-language REPL-alike form: + * + * foo bar "newline are supported\n" and "\xff\x00otherstuff" + * + * The number of arguments is stored into *argc, and an array + * of sds is returned. + * + * The caller should free the resulting array of sds strings with + * sdsfreesplitres(). + * + * Note that sdscatrepr() is able to convert back a string into + * a quoted string in the same format sdssplitargs() is able to parse. + * + * The function returns the allocated tokens on success, even when the + * input string is empty, or NULL if the input contains unbalanced + * quotes or closed quotes followed by non space characters + * as in: "foo"bar or "foo' + */ +sds *sdssplitargs(const char *line, int *argc) { + const char *p = line; + char *current = NULL; + char **vector = NULL; + + *argc = 0; + while(1) { + /* skip blanks */ + while(*p && isspace(*p)) p++; + if (*p) { + /* get a token */ + int inq=0; /* set to 1 if we are in "quotes" */ + int insq=0; /* set to 1 if we are in 'single quotes' */ + int done=0; + + if (current == NULL) current = sdsempty(); + while(!done) { + if (inq) { + if (*p == '\\' && *(p+1) == 'x' && + is_hex_digit(*(p+2)) && + is_hex_digit(*(p+3))) + { + unsigned char byte; + + byte = (hex_digit_to_int(*(p+2))*16)+ + hex_digit_to_int(*(p+3)); + current = sdscatlen(current,(char*)&byte,1); + p += 3; + } else if (*p == '\\' && *(p+1)) { + char c; + + p++; + switch(*p) { + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'b': c = '\b'; break; + case 'a': c = '\a'; break; + default: c = *p; break; + } + current = sdscatlen(current,&c,1); + } else if (*p == '"') { + /* closing quote must be followed by a space or + * nothing at all. */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; + } else { + current = sdscatlen(current,p,1); + } + } else if (insq) { + if (*p == '\\' && *(p+1) == '\'') { + p++; + current = sdscatlen(current,"'",1); + } else if (*p == '\'') { + /* closing quote must be followed by a space or + * nothing at all. */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; + } else { + current = sdscatlen(current,p,1); + } + } else { + switch(*p) { + case ' ': + case '\n': + case '\r': + case '\t': + case '\0': + done=1; + break; + case '"': + inq=1; + break; + case '\'': + insq=1; + break; + default: + current = sdscatlen(current,p,1); + break; + } + } + if (*p) p++; + } + /* add the token to the vector */ + vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); + vector[*argc] = current; + (*argc)++; + current = NULL; + } else { + /* Even on empty input string return something not NULL. */ + if (vector == NULL) vector = s_malloc(sizeof(void*)); + return vector; + } + } + +err: + while((*argc)--) + sdsfree(vector[*argc]); + s_free(vector); + if (current) sdsfree(current); + *argc = 0; + return NULL; +} + +/* Modify the string substituting all the occurrences of the set of + * characters specified in the 'from' string to the corresponding character + * in the 'to' array. + * + * For instance: sdsmapchars(mystring, "ho", "01", 2) + * will have the effect of turning the string "hello" into "0ell1". + * + * The function returns the sds string pointer, that is always the same + * as the input pointer since no resize is needed. */ +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { + size_t j, i, l = sdslen(s); + + for (j = 0; j < l; j++) { + for (i = 0; i < setlen; i++) { + if (s[j] == from[i]) { + s[j] = to[i]; + break; + } + } + } + return s; +} + +/* Join an array of C strings using the specified separator (also a C string). + * Returns the result as an sds string. */ +sds sdsjoin(char **argv, int argc, char *sep) { + sds join = sdsempty(); + int j; + + for (j = 0; j < argc; j++) { + join = sdscat(join, argv[j]); + if (j != argc-1) join = sdscat(join,sep); + } + return join; +} + +/* Like sdsjoin, but joins an array of SDS strings. */ +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { + sds join = sdsempty(); + int j; + + for (j = 0; j < argc; j++) { + join = sdscatsds(join, argv[j]); + if (j != argc-1) join = sdscatlen(join,sep,seplen); + } + return join; +} + +/* Wrappers to the allocators used by SDS. Note that SDS will actually + * just use the macros defined into sdsalloc.h in order to avoid to pay + * the overhead of function calls. Here we define these wrappers only for + * the programs SDS is linked to, if they want to touch the SDS internals + * even if they use a different allocator. */ +void *sds_malloc(size_t size) { return s_malloc(size); } +void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); } +void sds_free(void *ptr) { s_free(ptr); } + +#if defined(SDS_TEST_MAIN) +#include +#include "testhelp.h" +#include "limits.h" + +#define UNUSED(x) (void)(x) +int sdsTest(void) { + { + sds x = sdsnew("foo"), y; + + test_cond("Create a string and obtain the length", + sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) + + sdsfree(x); + x = sdsnewlen("foo",2); + test_cond("Create a string with specified length", + sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) + + x = sdscat(x,"bar"); + test_cond("Strings concatenation", + sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); + + x = sdscpy(x,"a"); + test_cond("sdscpy() against an originally longer string", + sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) + + x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); + test_cond("sdscpy() against an originally shorter string", + sdslen(x) == 33 && + memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) + + sdsfree(x); + x = sdscatprintf(sdsempty(),"%d",123); + test_cond("sdscatprintf() seems working in the base case", + sdslen(x) == 3 && memcmp(x,"123\0",4) == 0) + + sdsfree(x); + x = sdsnew("--"); + x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); + test_cond("sdscatfmt() seems working in the base case", + sdslen(x) == 60 && + memcmp(x,"--Hello Hi! World -9223372036854775808," + "9223372036854775807--",60) == 0) + printf("[%s]\n",x); + + sdsfree(x); + x = sdsnew("--"); + x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); + test_cond("sdscatfmt() seems working with unsigned numbers", + sdslen(x) == 35 && + memcmp(x,"--4294967295,18446744073709551615--",35) == 0) + + sdsfree(x); + x = sdsnew(" x "); + sdstrim(x," x"); + test_cond("sdstrim() works when all chars match", + sdslen(x) == 0) + + sdsfree(x); + x = sdsnew(" x "); + sdstrim(x," "); + test_cond("sdstrim() works when a single char remains", + sdslen(x) == 1 && x[0] == 'x') + + sdsfree(x); + x = sdsnew("xxciaoyyy"); + sdstrim(x,"xy"); + test_cond("sdstrim() correctly trims characters", + sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) + + y = sdsdup(x); + sdsrange(y,1,1); + test_cond("sdsrange(...,1,1)", + sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,1,-1); + test_cond("sdsrange(...,1,-1)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,-2,-1); + test_cond("sdsrange(...,-2,-1)", + sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,2,1); + test_cond("sdsrange(...,2,1)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,1,100); + test_cond("sdsrange(...,1,100)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,100,100); + test_cond("sdsrange(...,100,100)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("foo"); + y = sdsnew("foa"); + test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("bar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("aar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) + + sdsfree(y); + sdsfree(x); + x = sdsnewlen("\a\n\0foo\r",7); + y = sdscatrepr(sdsempty(),x,sdslen(x)); + test_cond("sdscatrepr(...data...)", + memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) + + { + unsigned int oldfree; + char *p; + int step = 10, j, i; + + sdsfree(x); + sdsfree(y); + x = sdsnew("0"); + test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0); + + /* Run the test a few times in order to hit the first two + * SDS header types. */ + for (i = 0; i < 10; i++) { + int oldlen = sdslen(x); + x = sdsMakeRoomFor(x,step); + int type = x[-1]&SDS_TYPE_MASK; + + test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen); + if (type != SDS_TYPE_5) { + test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step); + oldfree = sdsavail(x); + } + p = x+oldlen; + for (j = 0; j < step; j++) { + p[j] = 'A'+j; + } + sdsIncrLen(x,step); + } + test_cond("sdsMakeRoomFor() content", + memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0); + test_cond("sdsMakeRoomFor() final length",sdslen(x)==101); + + sdsfree(x); + } + } + test_report() + return 0; +} +#endif + +#ifdef SDS_TEST_MAIN +int main(void) { + return sdsTest(); +} +#endif diff --git a/ext/hiredis-0.14.1/sds.h b/ext/hiredis-0.14.1/sds.h new file mode 100644 index 000000000..13be75a9f --- /dev/null +++ b/ext/hiredis-0.14.1/sds.h @@ -0,0 +1,273 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, Redis Labs, Inc + * 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. + */ + +#ifndef __SDS_H +#define __SDS_H + +#define SDS_MAX_PREALLOC (1024*1024) + +#include +#include +#include + +typedef char *sds; + +/* Note: sdshdr5 is never used, we just access the flags byte directly. + * However is here to document the layout of type 5 SDS strings. */ +struct __attribute__ ((__packed__)) sdshdr5 { + unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr8 { + uint8_t len; /* used */ + uint8_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr16 { + uint16_t len; /* used */ + uint16_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr32 { + uint32_t len; /* used */ + uint32_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr64 { + uint64_t len; /* used */ + uint64_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; + +#define SDS_TYPE_5 0 +#define SDS_TYPE_8 1 +#define SDS_TYPE_16 2 +#define SDS_TYPE_32 3 +#define SDS_TYPE_64 4 +#define SDS_TYPE_MASK 7 +#define SDS_TYPE_BITS 3 +#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))); +#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) +#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) + +static inline size_t sdslen(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->len; + case SDS_TYPE_16: + return SDS_HDR(16,s)->len; + case SDS_TYPE_32: + return SDS_HDR(32,s)->len; + case SDS_TYPE_64: + return SDS_HDR(64,s)->len; + } + return 0; +} + +static inline size_t sdsavail(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { + return 0; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + return sh->alloc - sh->len; + } + } + return 0; +} + +static inline void sdssetlen(sds s, size_t newlen) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + { + unsigned char *fp = ((unsigned char*)s)-1; + *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); + } + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->len = newlen; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->len = newlen; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->len = newlen; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->len = newlen; + break; + } +} + +static inline void sdsinclen(sds s, size_t inc) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + { + unsigned char *fp = ((unsigned char*)s)-1; + unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc; + *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); + } + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->len += inc; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->len += inc; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->len += inc; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->len += inc; + break; + } +} + +/* sdsalloc() = sdsavail() + sdslen() */ +static inline size_t sdsalloc(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->alloc; + case SDS_TYPE_16: + return SDS_HDR(16,s)->alloc; + case SDS_TYPE_32: + return SDS_HDR(32,s)->alloc; + case SDS_TYPE_64: + return SDS_HDR(64,s)->alloc; + } + return 0; +} + +static inline void sdssetalloc(sds s, size_t newlen) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + /* Nothing to do, this type has no total allocation info. */ + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->alloc = newlen; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->alloc = newlen; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->alloc = newlen; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->alloc = newlen; + break; + } +} + +sds sdsnewlen(const void *init, size_t initlen); +sds sdsnew(const char *init); +sds sdsempty(void); +sds sdsdup(const sds s); +void sdsfree(sds s); +sds sdsgrowzero(sds s, size_t len); +sds sdscatlen(sds s, const void *t, size_t len); +sds sdscat(sds s, const char *t); +sds sdscatsds(sds s, const sds t); +sds sdscpylen(sds s, const char *t, size_t len); +sds sdscpy(sds s, const char *t); + +sds sdscatvprintf(sds s, const char *fmt, va_list ap); +#ifdef __GNUC__ +sds sdscatprintf(sds s, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +#else +sds sdscatprintf(sds s, const char *fmt, ...); +#endif + +sds sdscatfmt(sds s, char const *fmt, ...); +sds sdstrim(sds s, const char *cset); +void sdsrange(sds s, int start, int end); +void sdsupdatelen(sds s); +void sdsclear(sds s); +int sdscmp(const sds s1, const sds s2); +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); +void sdsfreesplitres(sds *tokens, int count); +void sdstolower(sds s); +void sdstoupper(sds s); +sds sdsfromlonglong(long long value); +sds sdscatrepr(sds s, const char *p, size_t len); +sds *sdssplitargs(const char *line, int *argc); +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); +sds sdsjoin(char **argv, int argc, char *sep); +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); + +/* Low level functions exposed to the user API */ +sds sdsMakeRoomFor(sds s, size_t addlen); +void sdsIncrLen(sds s, int incr); +sds sdsRemoveFreeSpace(sds s); +size_t sdsAllocSize(sds s); +void *sdsAllocPtr(sds s); + +/* Export the allocator used by SDS to the program using SDS. + * Sometimes the program SDS is linked to, may use a different set of + * allocators, but may want to allocate or free things that SDS will + * respectively free or allocate. */ +void *sds_malloc(size_t size); +void *sds_realloc(void *ptr, size_t size); +void sds_free(void *ptr); + +#ifdef REDIS_TEST +int sdsTest(int argc, char *argv[]); +#endif + +#endif diff --git a/ext/hiredis-0.14.1/sdsalloc.h b/ext/hiredis-0.14.1/sdsalloc.h new file mode 100644 index 000000000..f43023c48 --- /dev/null +++ b/ext/hiredis-0.14.1/sdsalloc.h @@ -0,0 +1,42 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, Redis Labs, Inc + * 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. + */ + +/* SDS allocator selection. + * + * This file is used in order to change the SDS allocator at compile time. + * Just define the following defines to what you want to use. Also add + * the include of your alternate allocator if needed (not needed in order + * to use the default libc allocator). */ + +#define s_malloc malloc +#define s_realloc realloc +#define s_free free diff --git a/ext/hiredis-0.14.1/test.c b/ext/hiredis-0.14.1/test.c new file mode 100644 index 000000000..0f5bfe572 --- /dev/null +++ b/ext/hiredis-0.14.1/test.c @@ -0,0 +1,923 @@ +#include "fmacros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hiredis.h" +#include "net.h" + +enum connection_type { + CONN_TCP, + CONN_UNIX, + CONN_FD +}; + +struct config { + enum connection_type type; + + struct { + const char *host; + int port; + struct timeval timeout; + } tcp; + + struct { + const char *path; + } unix_sock; +}; + +/* The following lines make up our testing "framework" :) */ +static int tests = 0, fails = 0; +#define test(_s) { printf("#%02d ", ++tests); printf(_s); } +#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} + +static long long usec(void) { + struct timeval tv; + gettimeofday(&tv,NULL); + return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; +} + +/* The assert() calls below have side effects, so we need assert() + * even if we are compiling without asserts (-DNDEBUG). */ +#ifdef NDEBUG +#undef assert +#define assert(e) (void)(e) +#endif + +static redisContext *select_database(redisContext *c) { + redisReply *reply; + + /* Switch to DB 9 for testing, now that we know we can chat. */ + reply = redisCommand(c,"SELECT 9"); + assert(reply != NULL); + freeReplyObject(reply); + + /* Make sure the DB is emtpy */ + reply = redisCommand(c,"DBSIZE"); + assert(reply != NULL); + if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) { + /* Awesome, DB 9 is empty and we can continue. */ + freeReplyObject(reply); + } else { + printf("Database #9 is not empty, test can not continue\n"); + exit(1); + } + + return c; +} + +static int disconnect(redisContext *c, int keep_fd) { + redisReply *reply; + + /* Make sure we're on DB 9. */ + reply = redisCommand(c,"SELECT 9"); + assert(reply != NULL); + freeReplyObject(reply); + reply = redisCommand(c,"FLUSHDB"); + assert(reply != NULL); + freeReplyObject(reply); + + /* Free the context as well, but keep the fd if requested. */ + if (keep_fd) + return redisFreeKeepFd(c); + redisFree(c); + return -1; +} + +static redisContext *connect(struct config config) { + redisContext *c = NULL; + + if (config.type == CONN_TCP) { + c = redisConnect(config.tcp.host, config.tcp.port); + } else if (config.type == CONN_UNIX) { + c = redisConnectUnix(config.unix_sock.path); + } else if (config.type == CONN_FD) { + /* Create a dummy connection just to get an fd to inherit */ + redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path); + if (dummy_ctx) { + int fd = disconnect(dummy_ctx, 1); + printf("Connecting to inherited fd %d\n", fd); + c = redisConnectFd(fd); + } + } else { + assert(NULL); + } + + if (c == NULL) { + printf("Connection error: can't allocate redis context\n"); + exit(1); + } else if (c->err) { + printf("Connection error: %s\n", c->errstr); + redisFree(c); + exit(1); + } + + return select_database(c); +} + +static void test_format_commands(void) { + char *cmd; + int len; + + test("Format command without interpolation: "); + len = redisFormatCommand(&cmd,"SET foo bar"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%s string interpolation: "); + len = redisFormatCommand(&cmd,"SET %s %s","foo","bar"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%s and an empty string: "); + len = redisFormatCommand(&cmd,"SET %s %s","foo",""); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(0+2)); + free(cmd); + + test("Format command with an empty string in between proper interpolations: "); + len = redisFormatCommand(&cmd,"SET %s %s","","foo"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 && + len == 4+4+(3+2)+4+(0+2)+4+(3+2)); + free(cmd); + + test("Format command with %%b string interpolation: "); + len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%b and an empty string: "); + len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(0+2)); + free(cmd); + + test("Format command with literal %%: "); + len = redisFormatCommand(&cmd,"SET %% %%"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 && + len == 4+4+(3+2)+4+(1+2)+4+(1+2)); + free(cmd); + + /* Vararg width depends on the type. These tests make sure that the + * width is correctly determined using the format and subsequent varargs + * can correctly be interpolated. */ +#define INTEGER_WIDTH_TEST(fmt, type) do { \ + type value = 123; \ + test("Format command with printf-delegation (" #type "): "); \ + len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \ + test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \ + len == 4+5+(12+2)+4+(9+2)); \ + free(cmd); \ +} while(0) + +#define FLOAT_WIDTH_TEST(type) do { \ + type value = 123.0; \ + test("Format command with printf-delegation (" #type "): "); \ + len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \ + test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \ + len == 4+5+(12+2)+4+(9+2)); \ + free(cmd); \ +} while(0) + + INTEGER_WIDTH_TEST("d", int); + INTEGER_WIDTH_TEST("hhd", char); + INTEGER_WIDTH_TEST("hd", short); + INTEGER_WIDTH_TEST("ld", long); + INTEGER_WIDTH_TEST("lld", long long); + INTEGER_WIDTH_TEST("u", unsigned int); + INTEGER_WIDTH_TEST("hhu", unsigned char); + INTEGER_WIDTH_TEST("hu", unsigned short); + INTEGER_WIDTH_TEST("lu", unsigned long); + INTEGER_WIDTH_TEST("llu", unsigned long long); + FLOAT_WIDTH_TEST(float); + FLOAT_WIDTH_TEST(double); + + test("Format command with invalid printf format: "); + len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); + test_cond(len == -1); + + const char *argv[3]; + argv[0] = "SET"; + argv[1] = "foo\0xxx"; + argv[2] = "bar"; + size_t lens[3] = { 3, 7, 3 }; + int argc = 3; + + test("Format command by passing argc/argv without lengths: "); + len = redisFormatCommandArgv(&cmd,argc,argv,NULL); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command by passing argc/argv with lengths: "); + len = redisFormatCommandArgv(&cmd,argc,argv,lens); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(7+2)+4+(3+2)); + free(cmd); + + sds sds_cmd; + + sds_cmd = sdsempty(); + test("Format command into sds by passing argc/argv without lengths: "); + len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL); + test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + sdsfree(sds_cmd); + + sds_cmd = sdsempty(); + test("Format command into sds by passing argc/argv with lengths: "); + len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens); + test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(7+2)+4+(3+2)); + sdsfree(sds_cmd); +} + +static void test_append_formatted_commands(struct config config) { + redisContext *c; + redisReply *reply; + char *cmd; + int len; + + c = connect(config); + + test("Append format command: "); + + len = redisFormatCommand(&cmd, "SET foo bar"); + + test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK); + + assert(redisGetReply(c, (void*)&reply) == REDIS_OK); + + free(cmd); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_reply_reader(void) { + redisReader *reader; + void *reply; + int ret; + int i; + + test("Error handling in reply parser: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"@foo\r\n",6); + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); + redisReaderFree(reader); + + /* when the reply already contains multiple items, they must be free'd + * on an error. valgrind will bark when this doesn't happen. */ + test("Memory cleanup in reply parser: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*2\r\n",4); + redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); + redisReaderFeed(reader,(char*)"@foo\r\n",6); + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); + redisReaderFree(reader); + + test("Set error on nested multi bulks with depth > 7: "); + reader = redisReaderCreate(); + + for (i = 0; i < 9; i++) { + redisReaderFeed(reader,(char*)"*1\r\n",4); + } + + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strncasecmp(reader->errstr,"No support for",14) == 0); + redisReaderFree(reader); + + test("Correctly parses LLONG_MAX: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ":9223372036854775807\r\n",22); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_INTEGER && + ((redisReply*)reply)->integer == LLONG_MAX); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when > LLONG_MAX: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ":9223372036854775808\r\n",22); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad integer value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Correctly parses LLONG_MIN: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ":-9223372036854775808\r\n",23); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_INTEGER && + ((redisReply*)reply)->integer == LLONG_MIN); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when < LLONG_MIN: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ":-9223372036854775809\r\n",23); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad integer value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when array < -1: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when bulk < -1: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "$-2\r\nasdf\r\n",11); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bulk string length out of range") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when array > INT_MAX: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + +#if LLONG_MAX > SIZE_MAX + test("Set error when bulk > SIZE_MAX: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bulk string length out of range") == 0); + freeReplyObject(reply); + redisReaderFree(reader); +#endif + + test("Works with NULL functions for reply: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"+OK\r\n",5); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); + redisReaderFree(reader); + + test("Works when a single newline (\\r\\n) covers two calls to feed: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"+OK\r",4); + ret = redisReaderGetReply(reader,&reply); + assert(ret == REDIS_OK && reply == NULL); + redisReaderFeed(reader,(char*)"\n",1); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); + redisReaderFree(reader); + + test("Don't reset state after protocol error: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"x",1); + ret = redisReaderGetReply(reader,&reply); + assert(ret == REDIS_ERR); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && reply == NULL); + redisReaderFree(reader); + + /* Regression test for issue #45 on GitHub. */ + test("Don't do empty allocation for empty multi bulk: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*0\r\n",4); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && + ((redisReply*)reply)->elements == 0); + freeReplyObject(reply); + redisReaderFree(reader); +} + +static void test_free_null(void) { + void *redisCtx = NULL; + void *reply = NULL; + + test("Don't fail when redisFree is passed a NULL value: "); + redisFree(redisCtx); + test_cond(redisCtx == NULL); + + test("Don't fail when freeReplyObject is passed a NULL value: "); + freeReplyObject(reply); + test_cond(reply == NULL); +} + +static void test_blocking_connection_errors(void) { + redisContext *c; + + test("Returns error when host cannot be resolved: "); + c = redisConnect((char*)"idontexist.test", 6379); + test_cond(c->err == REDIS_ERR_OTHER && + (strcmp(c->errstr,"Name or service not known") == 0 || + strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 || + strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || + strcmp(c->errstr,"No address associated with hostname") == 0 || + strcmp(c->errstr,"Temporary failure in name resolution") == 0 || + strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 || + strcmp(c->errstr,"no address associated with name") == 0)); + redisFree(c); + + test("Returns error when the port is not open: "); + c = redisConnect((char*)"localhost", 1); + test_cond(c->err == REDIS_ERR_IO && + strcmp(c->errstr,"Connection refused") == 0); + redisFree(c); + + test("Returns error when the unix_sock socket path doesn't accept connections: "); + c = redisConnectUnix((char*)"/tmp/idontexist.sock"); + test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ + redisFree(c); +} + +static void test_blocking_connection(struct config config) { + redisContext *c; + redisReply *reply; + + c = connect(config); + + test("Is able to deliver commands: "); + reply = redisCommand(c,"PING"); + test_cond(reply->type == REDIS_REPLY_STATUS && + strcasecmp(reply->str,"pong") == 0) + freeReplyObject(reply); + + test("Is a able to send commands verbatim: "); + reply = redisCommand(c,"SET foo bar"); + test_cond (reply->type == REDIS_REPLY_STATUS && + strcasecmp(reply->str,"ok") == 0) + freeReplyObject(reply); + + test("%%s String interpolation works: "); + reply = redisCommand(c,"SET %s %s","foo","hello world"); + freeReplyObject(reply); + reply = redisCommand(c,"GET foo"); + test_cond(reply->type == REDIS_REPLY_STRING && + strcmp(reply->str,"hello world") == 0); + freeReplyObject(reply); + + test("%%b String interpolation works: "); + reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11); + freeReplyObject(reply); + reply = redisCommand(c,"GET foo"); + test_cond(reply->type == REDIS_REPLY_STRING && + memcmp(reply->str,"hello\x00world",11) == 0) + + test("Binary reply length is correct: "); + test_cond(reply->len == 11) + freeReplyObject(reply); + + test("Can parse nil replies: "); + reply = redisCommand(c,"GET nokey"); + test_cond(reply->type == REDIS_REPLY_NIL) + freeReplyObject(reply); + + /* test 7 */ + test("Can parse integer replies: "); + reply = redisCommand(c,"INCR mycounter"); + test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) + freeReplyObject(reply); + + test("Can parse multi bulk replies: "); + freeReplyObject(redisCommand(c,"LPUSH mylist foo")); + freeReplyObject(redisCommand(c,"LPUSH mylist bar")); + reply = redisCommand(c,"LRANGE mylist 0 -1"); + test_cond(reply->type == REDIS_REPLY_ARRAY && + reply->elements == 2 && + !memcmp(reply->element[0]->str,"bar",3) && + !memcmp(reply->element[1]->str,"foo",3)) + freeReplyObject(reply); + + /* m/e with multi bulk reply *before* other reply. + * specifically test ordering of reply items to parse. */ + test("Can handle nested multi bulk replies: "); + freeReplyObject(redisCommand(c,"MULTI")); + freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1")); + freeReplyObject(redisCommand(c,"PING")); + reply = (redisCommand(c,"EXEC")); + test_cond(reply->type == REDIS_REPLY_ARRAY && + reply->elements == 2 && + reply->element[0]->type == REDIS_REPLY_ARRAY && + reply->element[0]->elements == 2 && + !memcmp(reply->element[0]->element[0]->str,"bar",3) && + !memcmp(reply->element[0]->element[1]->str,"foo",3) && + reply->element[1]->type == REDIS_REPLY_STATUS && + strcasecmp(reply->element[1]->str,"pong") == 0); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_blocking_connection_timeouts(struct config config) { + redisContext *c; + redisReply *reply; + ssize_t s; + const char *cmd = "DEBUG SLEEP 3\r\n"; + struct timeval tv; + + c = connect(config); + test("Successfully completes a command when the timeout is not exceeded: "); + reply = redisCommand(c,"SET foo fast"); + freeReplyObject(reply); + tv.tv_sec = 0; + tv.tv_usec = 10000; + redisSetTimeout(c, tv); + reply = redisCommand(c, "GET foo"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0); + freeReplyObject(reply); + disconnect(c, 0); + + c = connect(config); + test("Does not return a reply when the command times out: "); + s = write(c->fd, cmd, strlen(cmd)); + tv.tv_sec = 0; + tv.tv_usec = 10000; + redisSetTimeout(c, tv); + reply = redisCommand(c, "GET foo"); + test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0); + freeReplyObject(reply); + + test("Reconnect properly reconnects after a timeout: "); + redisReconnect(c); + reply = redisCommand(c, "PING"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); + freeReplyObject(reply); + + test("Reconnect properly uses owned parameters: "); + config.tcp.host = "foo"; + config.unix_sock.path = "foo"; + redisReconnect(c); + reply = redisCommand(c, "PING"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_blocking_io_errors(struct config config) { + redisContext *c; + redisReply *reply; + void *_reply; + int major, minor; + + /* Connect to target given by config. */ + c = connect(config); + { + /* Find out Redis version to determine the path for the next test */ + const char *field = "redis_version:"; + char *p, *eptr; + + reply = redisCommand(c,"INFO"); + p = strstr(reply->str,field); + major = strtol(p+strlen(field),&eptr,10); + p = eptr+1; /* char next to the first "." */ + minor = strtol(p,&eptr,10); + freeReplyObject(reply); + } + + test("Returns I/O error when the connection is lost: "); + reply = redisCommand(c,"QUIT"); + if (major > 2 || (major == 2 && minor > 0)) { + /* > 2.0 returns OK on QUIT and read() should be issued once more + * to know the descriptor is at EOF. */ + test_cond(strcasecmp(reply->str,"OK") == 0 && + redisGetReply(c,&_reply) == REDIS_ERR); + freeReplyObject(reply); + } else { + test_cond(reply == NULL); + } + + /* On 2.0, QUIT will cause the connection to be closed immediately and + * the read(2) for the reply on QUIT will set the error to EOF. + * On >2.0, QUIT will return with OK and another read(2) needed to be + * issued to find out the socket was closed by the server. In both + * conditions, the error will be set to EOF. */ + assert(c->err == REDIS_ERR_EOF && + strcmp(c->errstr,"Server closed the connection") == 0); + redisFree(c); + + c = connect(config); + test("Returns I/O error on socket timeout: "); + struct timeval tv = { 0, 1000 }; + assert(redisSetTimeout(c,tv) == REDIS_OK); + test_cond(redisGetReply(c,&_reply) == REDIS_ERR && + c->err == REDIS_ERR_IO && errno == EAGAIN); + redisFree(c); +} + +static void test_invalid_timeout_errors(struct config config) { + redisContext *c; + + test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: "); + + config.tcp.timeout.tv_sec = 0; + config.tcp.timeout.tv_usec = 10000001; + + c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); + + test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); + redisFree(c); + + test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); + + config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; + config.tcp.timeout.tv_usec = 0; + + c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); + + test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); + redisFree(c); +} + +static void test_throughput(struct config config) { + redisContext *c = connect(config); + redisReply **replies; + int i, num; + long long t1, t2; + + test("Throughput:\n"); + for (i = 0; i < 500; i++) + freeReplyObject(redisCommand(c,"LPUSH mylist foo")); + + num = 1000; + replies = malloc(sizeof(redisReply*)*num); + t1 = usec(); + for (i = 0; i < num; i++) { + replies[i] = redisCommand(c,"PING"); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + t1 = usec(); + for (i = 0; i < num; i++) { + replies[i] = redisCommand(c,"LRANGE mylist 0 499"); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); + assert(replies[i] != NULL && replies[i]->elements == 500); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + t1 = usec(); + for (i = 0; i < num; i++) { + replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0); + + num = 10000; + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) + redisAppendCommand(c,"PING"); + t1 = usec(); + for (i = 0; i < num; i++) { + assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) + redisAppendCommand(c,"LRANGE mylist 0 499"); + t1 = usec(); + for (i = 0; i < num; i++) { + assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); + assert(replies[i] != NULL && replies[i]->elements == 500); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) + redisAppendCommand(c,"INCRBY incrkey %d", 1000000); + t1 = usec(); + for (i = 0; i < num; i++) { + assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + + disconnect(c, 0); +} + +// static long __test_callback_flags = 0; +// static void __test_callback(redisContext *c, void *privdata) { +// ((void)c); +// /* Shift to detect execution order */ +// __test_callback_flags <<= 8; +// __test_callback_flags |= (long)privdata; +// } +// +// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) { +// ((void)c); +// /* Shift to detect execution order */ +// __test_callback_flags <<= 8; +// __test_callback_flags |= (long)privdata; +// if (reply) freeReplyObject(reply); +// } +// +// static redisContext *__connect_nonblock() { +// /* Reset callback flags */ +// __test_callback_flags = 0; +// return redisConnectNonBlock("127.0.0.1", port, NULL); +// } +// +// static void test_nonblocking_connection() { +// redisContext *c; +// int wdone = 0; +// +// test("Calls command callback when command is issued: "); +// c = __connect_nonblock(); +// redisSetCommandCallback(c,__test_callback,(void*)1); +// redisCommand(c,"PING"); +// test_cond(__test_callback_flags == 1); +// redisFree(c); +// +// test("Calls disconnect callback on redisDisconnect: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)2); +// redisDisconnect(c); +// test_cond(__test_callback_flags == 2); +// redisFree(c); +// +// test("Calls disconnect callback and free callback on redisFree: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)2); +// redisSetFreeCallback(c,__test_callback,(void*)4); +// redisFree(c); +// test_cond(__test_callback_flags == ((2 << 8) | 4)); +// +// test("redisBufferWrite against empty write buffer: "); +// c = __connect_nonblock(); +// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1); +// redisFree(c); +// +// test("redisBufferWrite against not yet connected fd: "); +// c = __connect_nonblock(); +// redisCommand(c,"PING"); +// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && +// strncmp(c->error,"write:",6) == 0); +// redisFree(c); +// +// test("redisBufferWrite against closed fd: "); +// c = __connect_nonblock(); +// redisCommand(c,"PING"); +// redisDisconnect(c); +// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && +// strncmp(c->error,"write:",6) == 0); +// redisFree(c); +// +// test("Process callbacks in the right sequence: "); +// c = __connect_nonblock(); +// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); +// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); +// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); +// +// /* Write output buffer */ +// wdone = 0; +// while(!wdone) { +// usleep(500); +// redisBufferWrite(c,&wdone); +// } +// +// /* Read until at least one callback is executed (the 3 replies will +// * arrive in a single packet, causing all callbacks to be executed in +// * a single pass). */ +// while(__test_callback_flags == 0) { +// assert(redisBufferRead(c) == REDIS_OK); +// redisProcessCallbacks(c); +// } +// test_cond(__test_callback_flags == 0x010203); +// redisFree(c); +// +// test("redisDisconnect executes pending callbacks with NULL reply: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)1); +// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); +// redisDisconnect(c); +// test_cond(__test_callback_flags == 0x0201); +// redisFree(c); +// } + +int main(int argc, char **argv) { + struct config cfg = { + .tcp = { + .host = "127.0.0.1", + .port = 6379 + }, + .unix_sock = { + .path = "/tmp/redis.sock" + } + }; + int throughput = 1; + int test_inherit_fd = 1; + + /* Ignore broken pipe signal (for I/O error tests). */ + signal(SIGPIPE, SIG_IGN); + + /* Parse command line options. */ + argv++; argc--; + while (argc) { + if (argc >= 2 && !strcmp(argv[0],"-h")) { + argv++; argc--; + cfg.tcp.host = argv[0]; + } else if (argc >= 2 && !strcmp(argv[0],"-p")) { + argv++; argc--; + cfg.tcp.port = atoi(argv[0]); + } else if (argc >= 2 && !strcmp(argv[0],"-s")) { + argv++; argc--; + cfg.unix_sock.path = argv[0]; + } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { + throughput = 0; + } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { + test_inherit_fd = 0; + } else { + fprintf(stderr, "Invalid argument: %s\n", argv[0]); + exit(1); + } + argv++; argc--; + } + + test_format_commands(); + test_reply_reader(); + test_blocking_connection_errors(); + test_free_null(); + + printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); + cfg.type = CONN_TCP; + test_blocking_connection(cfg); + test_blocking_connection_timeouts(cfg); + test_blocking_io_errors(cfg); + test_invalid_timeout_errors(cfg); + test_append_formatted_commands(cfg); + if (throughput) test_throughput(cfg); + + printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path); + cfg.type = CONN_UNIX; + test_blocking_connection(cfg); + test_blocking_connection_timeouts(cfg); + test_blocking_io_errors(cfg); + if (throughput) test_throughput(cfg); + + if (test_inherit_fd) { + printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path); + cfg.type = CONN_FD; + test_blocking_connection(cfg); + } + + + if (fails) { + printf("*** %d TESTS FAILED ***\n", fails); + return 1; + } + + printf("ALL TESTS PASSED\n"); + return 0; +} diff --git a/ext/hiredis-0.14.1/win32.h b/ext/hiredis-0.14.1/win32.h new file mode 100644 index 000000000..1a27c18f2 --- /dev/null +++ b/ext/hiredis-0.14.1/win32.h @@ -0,0 +1,42 @@ +#ifndef _WIN32_HELPER_INCLUDE +#define _WIN32_HELPER_INCLUDE +#ifdef _MSC_VER + +#ifndef inline +#define inline __inline +#endif + +#ifndef va_copy +#define va_copy(d,s) ((d) = (s)) +#endif + +#ifndef snprintf +#define snprintf c99_snprintf + +__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap) +{ + int count = -1; + + if (size != 0) + count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + + return count; +} + +__inline int c99_snprintf(char* str, size_t size, const char* format, ...) +{ + int count; + va_list ap; + + va_start(ap, format); + count = c99_vsnprintf(str, size, format, ap); + va_end(ap); + + return count; +} +#endif + +#endif +#endif \ No newline at end of file diff --git a/make-mac.mk b/make-mac.mk index d8b0c4c2a..fccfea7bf 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -29,7 +29,7 @@ ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o ext/http-parser/http ifeq ($(ZT_CONTROLLER),1) LIBS+=-L/usr/local/opt/libpq/lib -lpq - DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER + DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER_USE_REDIS -DZT_CONTROLLER INCLUDES+=-I/usr/local/opt/libpq/include -Iext/hiredis-vip-0.3.0 ONE_OBJS+=ext/hiredis-vip-0.3.0/adlist.o ext/hiredis-vip-0.3.0/async.o ext/hiredis-vip-0.3.0/command.o ext/hiredis-vip-0.3.0/crc16.o ext/hiredis-vip-0.3.0/dict.o ext/hiredis-vip-0.3.0/hiarray.o ext/hiredis-vip-0.3.0/hircluster.o ext/hiredis-vip-0.3.0/hiredis.o ext/hiredis-vip-0.3.0/hiutil.o ext/hiredis-vip-0.3.0/net.o ext/hiredis-vip-0.3.0/read.o ext/hiredis-vip-0.3.0/sds.o endif diff --git a/objects.mk b/objects.mk index efa2f3c0f..ed415a508 100644 --- a/objects.mk +++ b/objects.mk @@ -33,6 +33,7 @@ ONE_OBJS=\ controller/FileDB.o \ controller/LFDB.o \ controller/PostgreSQL.o \ + controller/Redis.o \ osdep/EthernetTap.o \ osdep/ManagedRoute.o \ osdep/Http.o \ diff --git a/service/OneService.cpp b/service/OneService.cpp index dcd4adb20..ba8405696 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -86,6 +86,8 @@ extern "C" { using json = nlohmann::json; #include "../controller/EmbeddedNetworkController.hpp" +#include "../controller/PostgreSQL.hpp" +#include "../controller/Redis.hpp" #include "../osdep/EthernetTap.hpp" #ifdef __WINDOWS__ #include "../osdep/WindowsEthernetTap.hpp" @@ -524,6 +526,8 @@ public: volatile bool _run; Mutex _run_m; + RedisConfig *_rc; + // end member variables ---------------------------------------------------- OneServiceImpl(const char *hp,unsigned int port) : @@ -559,6 +563,7 @@ public: ,_vaultPath("cubbyhole/zerotier") #endif ,_run(true) + ,_rc(NULL) { _ports[0] = 0; _ports[1] = 0; @@ -583,6 +588,7 @@ public: delete _portMapper; #endif delete _controller; + delete _rc; } virtual ReasonForTermination run() @@ -722,7 +728,7 @@ public: OSUtils::rmDashRf((_homePath + ZT_PATH_SEPARATOR_S "iddb.d").c_str()); // Network controller is now enabled by default for desktop and server - _controller = new EmbeddedNetworkController(_node,_homePath.c_str(),_controllerDbPath.c_str(),_ports[0]); + _controller = new EmbeddedNetworkController(_node,_homePath.c_str(),_controllerDbPath.c_str(),_ports[0], _rc); _node->setNetconfMaster((void *)_controller); // Join existing networks in networks.d @@ -986,7 +992,17 @@ public: if (cdbp.length() > 0) _controllerDbPath = cdbp; +#ifdef ZT_CONTROLLER_USE_LIBPQ // TODO: Redis config + json &redis = settings["redis"]; + if (redis.is_object() && _rc == NULL) { + _rc = new RedisConfig; + _rc->hostname = OSUtils::jsonString(redis["hostname"],""); + _rc->port = redis["port"]; + _rc->password = OSUtils::jsonString(redis["password"],""); + _rc->clusterMode = OSUtils::jsonBool(redis["clusterMode"], false); + } +#endif // Bind to wildcard instead of to specific interfaces (disables full tunnel capability) json &bind = settings["bind"]; From b5c661c5d590d11b701b54fb157f56950815a224 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 11 May 2020 15:06:10 -0700 Subject: [PATCH 049/362] add libhiredis.a for mac --- ext/hiredis-0.14.1/lib/macos/libhiredis.a | Bin 0 -> 260368 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ext/hiredis-0.14.1/lib/macos/libhiredis.a diff --git a/ext/hiredis-0.14.1/lib/macos/libhiredis.a b/ext/hiredis-0.14.1/lib/macos/libhiredis.a new file mode 100644 index 0000000000000000000000000000000000000000..f05e58f354ae2d18e739d42a105aac16d7bc9aae GIT binary patch literal 260368 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

FvN`Rfv+f3HZdHlRkiTKo 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 Cl30!a literal 0 HcmV?d00001 diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command.h new file mode 100644 index 000000000..3a4b24c5e --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command.h @@ -0,0 +1,2233 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_H + +#include +#include +#include +#include +#include "connection.h" +#include "command_options.h" +#include "command_args.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace cmd { + +// CONNECTION command. +inline void auth(Connection &connection, const StringView &password) { + connection.send("AUTH %b", password.data(), password.size()); +} + +inline void echo(Connection &connection, const StringView &msg) { + connection.send("ECHO %b", msg.data(), msg.size()); +} + +inline void ping(Connection &connection) { + connection.send("PING"); +} + +inline void quit(Connection &connection) { + connection.send("QUIT"); +} + +inline void ping(Connection &connection, const StringView &msg) { + // If *msg* is empty, Redis returns am empty reply of REDIS_REPLY_STRING type. + connection.send("PING %b", msg.data(), msg.size()); +} + +inline void select(Connection &connection, long long idx) { + connection.send("SELECT %lld", idx); +} + +inline void swapdb(Connection &connection, long long idx1, long long idx2) { + connection.send("SWAPDB %lld %lld", idx1, idx2); +} + +// SERVER commands. + +inline void bgrewriteaof(Connection &connection) { + connection.send("BGREWRITEAOF"); +} + +inline void bgsave(Connection &connection) { + connection.send("BGSAVE"); +} + +inline void dbsize(Connection &connection) { + connection.send("DBSIZE"); +} + +inline void flushall(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHALL ASYNC"); + } else { + connection.send("FLUSHALL"); + } +} + +inline void flushdb(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHDB ASYNC"); + } else { + connection.send("FLUSHDB"); + } +} + +inline void info(Connection &connection) { + connection.send("INFO"); +} + +inline void info(Connection &connection, const StringView §ion) { + connection.send("INFO %b", section.data(), section.size()); +} + +inline void lastsave(Connection &connection) { + connection.send("LASTSAVE"); +} + +inline void save(Connection &connection) { + connection.send("SAVE"); +} + +// KEY commands. + +inline void del(Connection &connection, const StringView &key) { + connection.send("DEL %b", key.data(), key.size()); +} + +template +inline void del_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "DEL" << std::make_pair(first, last); + + connection.send(args); +} + +inline void dump(Connection &connection, const StringView &key) { + connection.send("DUMP %b", key.data(), key.size()); +} + +inline void exists(Connection &connection, const StringView &key) { + connection.send("EXISTS %b", key.data(), key.size()); +} + +template +inline void exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void expire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("EXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void expireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("EXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void keys(Connection &connection, const StringView &pattern) { + connection.send("KEYS %b", pattern.data(), pattern.size()); +} + +inline void move(Connection &connection, const StringView &key, long long db) { + connection.send("MOVE %b %lld", + key.data(), key.size(), + db); +} + +inline void persist(Connection &connection, const StringView &key) { + connection.send("PERSIST %b", key.data(), key.size()); +} + +inline void pexpire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("PEXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void pexpireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("PEXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void pttl(Connection &connection, const StringView &key) { + connection.send("PTTL %b", key.data(), key.size()); +} + +inline void randomkey(Connection &connection) { + connection.send("RANDOMKEY"); +} + +inline void rename(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAME %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +inline void renamenx(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAMENX %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +void restore(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + bool replace); + +inline void scan(Connection &connection, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SCAN %lld MATCH %b COUNT %lld", + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void touch(Connection &connection, const StringView &key) { + connection.send("TOUCH %b", key.data(), key.size()); +} + +template +inline void touch_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "TOUCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void ttl(Connection &connection, const StringView &key) { + connection.send("TTL %b", key.data(), key.size()); +} + +inline void type(Connection &connection, const StringView &key) { + connection.send("TYPE %b", key.data(), key.size()); +} + +inline void unlink(Connection &connection, const StringView &key) { + connection.send("UNLINK %b", key.data(), key.size()); +} + +template +inline void unlink_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "UNLINK" << std::make_pair(first, last); + + connection.send(args); +} + +inline void wait(Connection &connection, long long numslave, long long timeout) { + connection.send("WAIT %lld %lld", numslave, timeout); +} + +// STRING commands. + +inline void append(Connection &connection, const StringView &key, const StringView &str) { + connection.send("APPEND %b %b", + key.data(), key.size(), + str.data(), str.size()); +} + +inline void bitcount(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("BITCOUNT %b %lld %lld", + key.data(), key.size(), + start, end); +} + +void bitop(Connection &connection, + BitOp op, + const StringView &destination, + const StringView &key); + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last); + +inline void bitpos(Connection &connection, + const StringView &key, + long long bit, + long long start, + long long end) { + connection.send("BITPOS %b %lld %lld %lld", + key.data(), key.size(), + bit, + start, + end); +} + +inline void decr(Connection &connection, const StringView &key) { + connection.send("DECR %b", key.data(), key.size()); +} + +inline void decrby(Connection &connection, const StringView &key, long long decrement) { + connection.send("DECRBY %b %lld", + key.data(), key.size(), + decrement); +} + +inline void get(Connection &connection, const StringView &key) { + connection.send("GET %b", + key.data(), key.size()); +} + +inline void getbit(Connection &connection, const StringView &key, long long offset) { + connection.send("GETBIT %b %lld", + key.data(), key.size(), + offset); +} + +inline void getrange(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("GETRANGE %b %lld %lld", + key.data(), key.size(), + start, + end); +} + +inline void getset(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("GETSET %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void incr(Connection &connection, const StringView &key) { + connection.send("INCR %b", key.data(), key.size()); +} + +inline void incrby(Connection &connection, const StringView &key, long long increment) { + connection.send("INCRBY %b %lld", + key.data(), key.size(), + increment); +} + +inline void incrbyfloat(Connection &connection, const StringView &key, double increment) { + connection.send("INCRBYFLOAT %b %f", + key.data(), key.size(), + increment); +} + +template +inline void mget(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MGET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void mset(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void msetnx(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSETNX" << std::make_pair(first, last); + + connection.send(args); +} + +inline void psetex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("PSETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +void set(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + UpdateType type); + +inline void setex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("SETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +inline void setnx(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("SETNX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void setrange(Connection &connection, + const StringView &key, + long long offset, + const StringView &val) { + connection.send("SETRANGE %b %lld %b", + key.data(), key.size(), + offset, + val.data(), val.size()); +} + +inline void strlen(Connection &connection, const StringView &key) { + connection.send("STRLEN %b", key.data(), key.size()); +} + +// LIST commands. + +inline void blpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BLPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void blpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BLPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BRPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void brpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BRPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpoplpush(Connection &connection, + const StringView &source, + const StringView &destination, + long long timeout) { + connection.send("BRPOPLPUSH %b %b %lld", + source.data(), source.size(), + destination.data(), destination.size(), + timeout); +} + +inline void lindex(Connection &connection, const StringView &key, long long index) { + connection.send("LINDEX %b %lld", + key.data(), key.size(), + index); +} + +void linsert(Connection &connection, + const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + +inline void llen(Connection &connection, + const StringView &key) { + connection.send("LLEN %b", key.data(), key.size()); +} + +inline void lpop(Connection &connection, const StringView &key) { + connection.send("LPOP %b", + key.data(), key.size()); +} + +inline void lpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void lpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "LPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void lpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void lrange(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void lrem(Connection &connection, + const StringView &key, + long long count, + const StringView &val) { + connection.send("LREM %b %lld %b", + key.data(), key.size(), + count, + val.data(), val.size()); +} + +inline void lset(Connection &connection, + const StringView &key, + long long index, + const StringView &val) { + connection.send("LSET %b %lld %b", + key.data(), key.size(), + index, + val.data(), val.size()); +} + +inline void ltrim(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LTRIM %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void rpop(Connection &connection, const StringView &key) { + connection.send("RPOP %b", key.data(), key.size()); +} + +inline void rpoplpush(Connection &connection, + const StringView &source, + const StringView &destination) { + connection.send("RPOPLPUSH %b %b", + source.data(), source.size(), + destination.data(), destination.size()); +} + +inline void rpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void rpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "RPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void rpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +// HASH commands. + +inline void hdel(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HDEL %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +template +inline void hdel_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hexists(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HEXISTS %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hget(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HGET %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hgetall(Connection &connection, const StringView &key) { + connection.send("HGETALL %b", key.data(), key.size()); +} + +inline void hincrby(Connection &connection, + const StringView &key, + const StringView &field, + long long increment) { + connection.send("HINCRBY %b %b %lld", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hincrbyfloat(Connection &connection, + const StringView &key, + const StringView &field, + double increment) { + connection.send("HINCRBYFLOAT %b %b %f", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hkeys(Connection &connection, const StringView &key) { + connection.send("HKEYS %b", key.data(), key.size()); +} + +inline void hlen(Connection &connection, const StringView &key) { + connection.send("HLEN %b", key.data(), key.size()); +} + +template +inline void hmget(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMGET" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void hmset(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMSET" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("HSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void hset(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSET %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +inline void hsetnx(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSETNX %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +inline void hstrlen(Connection &connection, + const StringView &key, + const StringView &field) { + connection.send("HSTRLEN %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hvals(Connection &connection, const StringView &key) { + connection.send("HVALS %b", key.data(), key.size()); +} + +// SET commands + +inline void sadd(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SADD %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void sadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void scard(Connection &connection, const StringView &key) { + connection.send("SCARD %b", key.data(), key.size()); +} + +template +inline void sdiff(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFF" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sdiffstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SDIFFSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sdiffstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFFSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void sinter(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTER" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sinterstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SINTERSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTERSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +inline void sismember(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SISMEMBER %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void smembers(Connection &connection, const StringView &key) { + connection.send("SMEMBERS %b", key.data(), key.size()); +} + +inline void smove(Connection &connection, + const StringView &source, + const StringView &destination, + const StringView &member) { + connection.send("SMOVE %b %b %b", + source.data(), source.size(), + destination.data(), destination.size(), + member.data(), member.size()); +} + +inline void spop(Connection &connection, const StringView &key) { + connection.send("SPOP %b", key.data(), key.size()); +} + +inline void spop_range(Connection &connection, const StringView &key, long long count) { + connection.send("SPOP %b %lld", + key.data(), key.size(), + count); +} + +inline void srandmember(Connection &connection, const StringView &key) { + connection.send("SRANDMEMBER %b", key.data(), key.size()); +} + +inline void srandmember_range(Connection &connection, + const StringView &key, + long long count) { + connection.send("SRANDMEMBER %b %lld", + key.data(), key.size(), + count); +} + +inline void srem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void srem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void sscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +template +inline void sunion(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNION" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sunionstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SUNIONSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNIONSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// Sorted Set commands. + +inline void bzpopmax(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMAX %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmax_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMAX" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void bzpopmin(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMIN %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmin_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMIN" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed); + +inline void zadd(Connection &connection, + const StringView &key, + const StringView &member, + double score, + UpdateType type, + bool changed) { + auto tmp = {std::make_pair(member, score)}; + + zadd_range(connection, key, tmp.begin(), tmp.end(), type, changed); +} + +inline void zcard(Connection &connection, const StringView &key) { + connection.send("ZCARD %b", key.data(), key.size()); +} + +template +inline void zcount(Connection &connection, + const StringView &key, + const Interval &interval) { + connection.send("ZCOUNT %b %s %s", + key.data(), key.size(), + interval.min().c_str(), + interval.max().c_str()); +} + +inline void zincrby(Connection &connection, + const StringView &key, + double increment, + const StringView &member) { + connection.send("ZINCRBY %b %f %b", + key.data(), key.size(), + increment, + member.data(), member.size()); +} + +inline void zinterstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZINTERSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +template +inline void zlexcount(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZLEXCOUNT %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zpopmax(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMAX %b %lld", + key.data(), key.size(), + count); +} + +inline void zpopmin(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMIN %b %lld", + key.data(), key.size(), + count); +} + +inline void zrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); +} + +template +void zrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } else { + connection.send("ZRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } +} + +inline void zrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zrem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void zrem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "ZREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void zremrangebylex(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYLEX %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zremrangebyrank(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("zremrangebyrank %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +template +inline void zremrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYSCORE %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zrevrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZREVRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZREVRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrevrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREVRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); +} + +template +void zrevrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZREVRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } else { + connection.send("ZREVRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } +} + +inline void zrevrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREVRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("ZSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void zscore(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZSCORE %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zunionstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZUNIONSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +// HYPERLOGLOG commands. + +inline void pfadd(Connection &connection, + const StringView &key, + const StringView &element) { + connection.send("PFADD %b %b", + key.data(), key.size(), + element.data(), element.size()); +} + +template +inline void pfadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfcount(Connection &connection, const StringView &key) { + connection.send("PFCOUNT %b", key.data(), key.size()); +} + +template +inline void pfcount_range(Connection &connection, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFCOUNT" << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfmerge(Connection &connection, const StringView &destination, const StringView &key) { + connection.send("PFMERGE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void pfmerge_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFMERGE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// GEO commands. + +inline void geoadd(Connection &connection, + const StringView &key, + const std::tuple &member) { + const auto &mem = std::get<0>(member); + + connection.send("GEOADD %b %f %f %b", + key.data(), key.size(), + std::get<1>(member), + std::get<2>(member), + mem.data(), mem.size()); +} + +template +inline void geoadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOADD" << key; + + while (first != last) { + const auto &member = *first; + args << std::get<1>(member) << std::get<2>(member) << std::get<0>(member); + ++first; + } + + connection.send(args); +} + +void geodist(Connection &connection, + const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit); + +template +inline void geohash_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOHASH" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void geopos_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOPOS" << key << std::make_pair(first, last); + + connection.send(args); +} + +void georadius(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadius_store(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void georadiusbymember(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadiusbymember_store(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +// SCRIPTING commands. + +inline void eval(Connection &connection, + const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + CmdArgs cmd_args; + + cmd_args << "EVAL" << script << keys.size() + << std::make_pair(keys.begin(), keys.end()) + << std::make_pair(args.begin(), args.end()); + + connection.send(cmd_args); +} + +inline void evalsha(Connection &connection, + const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + CmdArgs cmd_args; + + cmd_args << "EVALSHA" << script << keys.size() + << std::make_pair(keys.begin(), keys.end()) + << std::make_pair(args.begin(), args.end()); + + connection.send(cmd_args); +} + +inline void script_exists(Connection &connection, const StringView &sha) { + connection.send("SCRIPT EXISTS %b", sha.data(), sha.size()); +} + +template +inline void script_exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SCRIPT" << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void script_flush(Connection &connection) { + connection.send("SCRIPT FLUSH"); +} + +inline void script_kill(Connection &connection) { + connection.send("SCRIPT KILL"); +} + +inline void script_load(Connection &connection, const StringView &script) { + connection.send("SCRIPT LOAD %b", script.data(), script.size()); +} + +// PUBSUB commands. + +inline void psubscribe(Connection &connection, const StringView &pattern) { + connection.send("PSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void psubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void publish(Connection &connection, + const StringView &channel, + const StringView &message) { + connection.send("PUBLISH %b %b", + channel.data(), channel.size(), + message.data(), message.size()); +} + +inline void punsubscribe(Connection &connection) { + connection.send("PUNSUBSCRIBE"); +} + +inline void punsubscribe(Connection &connection, const StringView &pattern) { + connection.send("PUNSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void punsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PUNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PUNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void subscribe(Connection &connection, const StringView &channel) { + connection.send("SUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void subscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("SUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "SUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void unsubscribe(Connection &connection) { + connection.send("UNSUBSCRIBE"); +} + +inline void unsubscribe(Connection &connection, const StringView &channel) { + connection.send("UNSUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void unsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "UNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +// Transaction commands. + +inline void discard(Connection &connection) { + connection.send("DISCARD"); +} + +inline void exec(Connection &connection) { + connection.send("EXEC"); +} + +inline void multi(Connection &connection) { + connection.send("MULTI"); +} + +inline void unwatch(Connection &connection, const StringView &key) { + connection.send("UNWATCH %b", key.data(), key.size()); +} + +template +inline void unwatch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNWATCH: no key specified"); + } + + CmdArgs args; + args << "UNWATCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void watch(Connection &connection, const StringView &key) { + connection.send("WATCH %b", key.data(), key.size()); +} + +template +inline void watch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("WATCH: no key specified"); + } + + CmdArgs args; + args << "WATCH" << std::make_pair(first, last); + + connection.send(args); +} + +// Stream commands. + +inline void xack(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XACK %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +template +void xack_range(Connection &connection, + const StringView &key, + const StringView &group, + Input first, + Input last) { + CmdArgs args; + args << "XACK" << key << group << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last) { + CmdArgs args; + args << "XADD" << key << id << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_maxlen_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + CmdArgs args; + args << "XADD" << key << "MAXLEN"; + + if (approx) { + args << "~"; + } + + args << count << id << std::make_pair(first, last); + + connection.send(args); +} + +inline void xclaim(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + const StringView &id) { + connection.send("XCLAIM %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size(), + min_idle_time, + id.data(), id.size()); +} + +template +void xclaim_range(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + Input first, + Input last) { + CmdArgs args; + args << "XCLAIM" << key << group << consumer << min_idle_time << std::make_pair(first, last); + + connection.send(args); +} + +inline void xdel(Connection &connection, const StringView &key, const StringView &id) { + connection.send("XDEL %b %b", key.data(), key.size(), id.data(), id.size()); +} + +template +void xdel_range(Connection &connection, const StringView &key, Input first, Input last) { + CmdArgs args; + args << "XDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void xgroup_create(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream) { + CmdArgs args; + args << "XGROUP" << "CREATE" << key << group << id; + + if (mkstream) { + args << "MKSTREAM"; + } + + connection.send(args); +} + +inline void xgroup_setid(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XGROUP SETID %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +inline void xgroup_destroy(Connection &connection, + const StringView &key, + const StringView &group) { + connection.send("XGROUP DESTROY %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xgroup_delconsumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer) { + connection.send("XGROUP DELCONSUMER %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size()); +} + +inline void xlen(Connection &connection, const StringView &key) { + connection.send("XLEN %b", key.data(), key.size()); +} + +inline void xpending(Connection &connection, const StringView &key, const StringView &group) { + connection.send("XPENDING %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xpending_detail(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XPENDING %b %b %b %b %lld", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xpending_per_consumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + connection.send("XPENDING %b %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count, + consumer.data(), consumer.size()); +} + +inline void xrange(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end) { + connection.send("XRANGE %b %b %b", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size()); +} + +inline void xrange_count(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xread(Connection &connection, + const StringView &key, + const StringView &id, + long long count) { + connection.send("XREAD COUNT %lld STREAMS %b %b", + count, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_range(Connection &connection, Input first, Input last, long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xread_block(Connection &connection, + const StringView &key, + const StringView &id, + long long timeout, + long long count) { + connection.send("XREAD COUNT %lld BLOCK %lld STREAMS %b %b", + count, + timeout, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_block_range(Connection &connection, + Input first, + Input last, + long long timeout, + long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "BLOCK" << timeout << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup_block(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_block_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xrevrange(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start) { + connection.send("XREVRANGE %b %b %b", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size()); +} + +inline void xrevrange_count(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + connection.send("XREVRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size(), + count); +} + +void xtrim(Connection &connection, const StringView &key, long long count, bool approx); + +namespace detail { + +void set_bitop(CmdArgs &args, BitOp op); + +void set_update_type(CmdArgs &args, UpdateType type); + +void set_aggregation_type(CmdArgs &args, Aggregation type); + +template +void zinterstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zinterstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +void set_geo_unit(CmdArgs &args, GeoUnit unit); + +void set_georadius_store_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void set_georadius_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +} + +} + +} + +} + +namespace sw { + +namespace redis { + +namespace cmd { + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + + detail::set_bitop(args, op); + + args << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + assert(first != last); + + CmdArgs args; + + args << "ZADD" << key; + + detail::set_update_type(args, type); + + if (changed) { + args << "CH"; + } + + while (first != last) { + // Swap the pair to pair. + args << first->second << first->first; + ++first; + } + + connection.send(args); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zinterstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zunionstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_args.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_args.h new file mode 100644 index 000000000..0beb71e5c --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_args.h @@ -0,0 +1,180 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H + +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +class CmdArgs { +public: + template + CmdArgs& append(Arg &&arg); + + template + CmdArgs& append(Arg &&arg, Args &&...args); + + // All overloads of operator<< are for internal use only. + CmdArgs& operator<<(const StringView &arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& operator<<(T &&arg); + + template + CmdArgs& operator<<(const std::pair &range); + + template + auto operator<<(const std::tuple &) -> + typename std::enable_if::type { + return *this; + } + + template + auto operator<<(const std::tuple &arg) -> + typename std::enable_if::type; + + const char** argv() { + return _argv.data(); + } + + const std::size_t* argv_len() { + return _argv_len.data(); + } + + std::size_t size() const { + return _argv.size(); + } + +private: + // Deep copy. + CmdArgs& _append(std::string arg); + + // Shallow copy. + CmdArgs& _append(const StringView &arg); + + // Shallow copy. + CmdArgs& _append(const char *arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& _append(T &&arg) { + return operator<<(std::forward(arg)); + } + + template + CmdArgs& _append(std::true_type, const std::pair &range); + + template + CmdArgs& _append(std::false_type, const std::pair &range); + + std::vector _argv; + std::vector _argv_len; + + std::list _args; +}; + +template +inline CmdArgs& CmdArgs::append(Arg &&arg) { + return _append(std::forward(arg)); +} + +template +inline CmdArgs& CmdArgs::append(Arg &&arg, Args &&...args) { + _append(std::forward(arg)); + + return append(std::forward(args)...); +} + +inline CmdArgs& CmdArgs::operator<<(const StringView &arg) { + _argv.push_back(arg.data()); + _argv_len.push_back(arg.size()); + + return *this; +} + +template +inline CmdArgs& CmdArgs::operator<<(const std::pair &range) { + return _append(IsKvPair())>::type>(), range); +} + +template ::type>::value, + int>::type> +inline CmdArgs& CmdArgs::operator<<(T &&arg) { + return _append(std::to_string(std::forward(arg))); +} + +template +auto CmdArgs::operator<<(const std::tuple &arg) -> + typename std::enable_if::type { + operator<<(std::get(arg)); + + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(std::string arg) { + _args.push_back(std::move(arg)); + return operator<<(_args.back()); +} + +inline CmdArgs& CmdArgs::_append(const StringView &arg) { + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(const char *arg) { + return operator<<(arg); +} + +template +CmdArgs& CmdArgs::_append(std::false_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << *first; + ++first; + } + + return *this; +} + +template +CmdArgs& CmdArgs::_append(std::true_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << first->first << first->second; + ++first; + } + + return *this; +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_options.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_options.h new file mode 100644 index 000000000..ca766c086 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_options.h @@ -0,0 +1,211 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H + +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +enum class UpdateType { + EXIST, + NOT_EXIST, + ALWAYS +}; + +enum class InsertPosition { + BEFORE, + AFTER +}; + +enum class BoundType { + CLOSED, + OPEN, + LEFT_OPEN, + RIGHT_OPEN +}; + +// (-inf, +inf) +template +class UnboundedInterval; + +// [min, max], (min, max), (min, max], [min, max) +template +class BoundedInterval; + +// [min, +inf), (min, +inf) +template +class LeftBoundedInterval; + +// (-inf, max], (-inf, max) +template +class RightBoundedInterval; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(double min, double max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(double min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(double max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(const std::string &min, const std::string &max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(const std::string &min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(const std::string &max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +struct LimitOptions { + long long offset = 0; + long long count = -1; +}; + +enum class Aggregation { + SUM, + MIN, + MAX +}; + +enum class BitOp { + AND, + OR, + XOR, + NOT +}; + +enum class GeoUnit { + M, + KM, + MI, + FT +}; + +template +struct WithCoord : TupleWithType, T> {}; + +template +struct WithDist : TupleWithType {}; + +template +struct WithHash : TupleWithType {}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection.h new file mode 100644 index 000000000..5ad419225 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection.h @@ -0,0 +1,194 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_H + +#include +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "reply.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +enum class ConnectionType { + TCP = 0, + UNIX +}; + +struct ConnectionOptions { +public: + ConnectionOptions() = default; + + explicit ConnectionOptions(const std::string &uri); + + ConnectionOptions(const ConnectionOptions &) = default; + ConnectionOptions& operator=(const ConnectionOptions &) = default; + + ConnectionOptions(ConnectionOptions &&) = default; + ConnectionOptions& operator=(ConnectionOptions &&) = default; + + ~ConnectionOptions() = default; + + ConnectionType type = ConnectionType::TCP; + + std::string host; + + int port = 6379; + + std::string path; + + std::string password; + + int db = 0; + + bool keep_alive = false; + + std::chrono::milliseconds connect_timeout{0}; + + std::chrono::milliseconds socket_timeout{0}; + +private: + ConnectionOptions _parse_options(const std::string &uri) const; + + ConnectionOptions _parse_tcp_options(const std::string &path) const; + + ConnectionOptions _parse_unix_options(const std::string &path) const; + + auto _split_string(const std::string &str, const std::string &delimiter) const -> + std::pair; +}; + +class CmdArgs; + +class Connection { +public: + explicit Connection(const ConnectionOptions &opts); + + Connection(const Connection &) = delete; + Connection& operator=(const Connection &) = delete; + + Connection(Connection &&) = default; + Connection& operator=(Connection &&) = default; + + ~Connection() = default; + + // Check if the connection is broken. Client needs to do this check + // before sending some command to the connection. If it's broken, + // client needs to reconnect it. + bool broken() const noexcept { + return _ctx->err != REDIS_OK; + } + + void reset() noexcept { + _ctx->err = 0; + } + + void reconnect(); + + auto last_active() const + -> std::chrono::time_point { + return _last_active; + } + + template + void send(const char *format, Args &&...args); + + void send(int argc, const char **argv, const std::size_t *argv_len); + + void send(CmdArgs &args); + + ReplyUPtr recv(); + + const ConnectionOptions& options() const { + return _opts; + } + + friend void swap(Connection &lhs, Connection &rhs) noexcept; + +private: + class Connector; + + struct ContextDeleter { + void operator()(redisContext *context) const { + if (context != nullptr) { + redisFree(context); + } + }; + }; + + using ContextUPtr = std::unique_ptr; + + void _set_options(); + + void _auth(); + + void _select_db(); + + redisContext* _context(); + + ContextUPtr _ctx; + + // The time that the connection is created or the time that + // the connection is used, i.e. *context()* is called. + std::chrono::time_point _last_active{}; + + ConnectionOptions _opts; +}; + +using ConnectionSPtr = std::shared_ptr; + +enum class Role { + MASTER, + SLAVE +}; + +// Inline implementaions. + +template +inline void Connection::send(const char *format, Args &&...args) { + auto ctx = _context(); + + assert(ctx != nullptr); + + if (redisAppendCommand(ctx, + format, + std::forward(args)...) != REDIS_OK) { + throw_error(*ctx, "Failed to send command"); + } + + assert(!broken()); +} + +inline redisContext* Connection::_context() { + _last_active = std::chrono::steady_clock::now(); + + return _ctx.get(); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection_pool.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection_pool.h new file mode 100644 index 000000000..6f2663ad7 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection_pool.h @@ -0,0 +1,115 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +struct ConnectionPoolOptions { + // Max number of connections, including both in-use and idle ones. + std::size_t size = 1; + + // Max time to wait for a connection. 0ms means client waits forever. + std::chrono::milliseconds wait_timeout{0}; + + // Max lifetime of a connection. 0ms means we never expire the connection. + std::chrono::milliseconds connection_lifetime{0}; +}; + +class ConnectionPool { +public: + ConnectionPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool(SimpleSentinel sentinel, + const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool() = default; + + ConnectionPool(ConnectionPool &&that); + ConnectionPool& operator=(ConnectionPool &&that); + + ConnectionPool(const ConnectionPool &) = delete; + ConnectionPool& operator=(const ConnectionPool &) = delete; + + ~ConnectionPool() = default; + + // Fetch a connection from pool. + Connection fetch(); + + ConnectionOptions connection_options(); + + void release(Connection connection); + + // Create a new connection. + Connection create(); + +private: + void _move(ConnectionPool &&that); + + // NOT thread-safe + Connection _create(); + + Connection _create(SimpleSentinel &sentinel, const ConnectionOptions &opts, bool locked); + + Connection _fetch(); + + void _wait_for_connection(std::unique_lock &lock); + + bool _need_reconnect(const Connection &connection, + const std::chrono::milliseconds &connection_lifetime) const; + + void _update_connection_opts(const std::string &host, int port) { + _opts.host = host; + _opts.port = port; + } + + bool _role_changed(const ConnectionOptions &opts) const { + return opts.port != _opts.port || opts.host != _opts.host; + } + + ConnectionOptions _opts; + + ConnectionPoolOptions _pool_opts; + + std::deque _pool; + + std::size_t _used_connections = 0; + + std::mutex _mutex; + + std::condition_variable _cv; + + SimpleSentinel _sentinel; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/errors.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/errors.h new file mode 100644 index 000000000..44d629e50 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/errors.h @@ -0,0 +1,159 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_ERRORS_H +#define SEWENEW_REDISPLUSPLUS_ERRORS_H + +#include +#include +#include + +namespace sw { + +namespace redis { + +enum ReplyErrorType { + ERR, + MOVED, + ASK +}; + +class Error : public std::exception { +public: + explicit Error(const std::string &msg) : _msg(msg) {} + + Error(const Error &) = default; + Error& operator=(const Error &) = default; + + Error(Error &&) = default; + Error& operator=(Error &&) = default; + + virtual ~Error() = default; + + virtual const char* what() const noexcept { + return _msg.data(); + } + +private: + std::string _msg; +}; + +class IoError : public Error { +public: + explicit IoError(const std::string &msg) : Error(msg) {} + + IoError(const IoError &) = default; + IoError& operator=(const IoError &) = default; + + IoError(IoError &&) = default; + IoError& operator=(IoError &&) = default; + + virtual ~IoError() = default; +}; + +class TimeoutError : public IoError { +public: + explicit TimeoutError(const std::string &msg) : IoError(msg) {} + + TimeoutError(const TimeoutError &) = default; + TimeoutError& operator=(const TimeoutError &) = default; + + TimeoutError(TimeoutError &&) = default; + TimeoutError& operator=(TimeoutError &&) = default; + + virtual ~TimeoutError() = default; +}; + +class ClosedError : public Error { +public: + explicit ClosedError(const std::string &msg) : Error(msg) {} + + ClosedError(const ClosedError &) = default; + ClosedError& operator=(const ClosedError &) = default; + + ClosedError(ClosedError &&) = default; + ClosedError& operator=(ClosedError &&) = default; + + virtual ~ClosedError() = default; +}; + +class ProtoError : public Error { +public: + explicit ProtoError(const std::string &msg) : Error(msg) {} + + ProtoError(const ProtoError &) = default; + ProtoError& operator=(const ProtoError &) = default; + + ProtoError(ProtoError &&) = default; + ProtoError& operator=(ProtoError &&) = default; + + virtual ~ProtoError() = default; +}; + +class OomError : public Error { +public: + explicit OomError(const std::string &msg) : Error(msg) {} + + OomError(const OomError &) = default; + OomError& operator=(const OomError &) = default; + + OomError(OomError &&) = default; + OomError& operator=(OomError &&) = default; + + virtual ~OomError() = default; +}; + +class ReplyError : public Error { +public: + explicit ReplyError(const std::string &msg) : Error(msg) {} + + ReplyError(const ReplyError &) = default; + ReplyError& operator=(const ReplyError &) = default; + + ReplyError(ReplyError &&) = default; + ReplyError& operator=(ReplyError &&) = default; + + virtual ~ReplyError() = default; +}; + +class WatchError : public Error { +public: + explicit WatchError() : Error("Watched key has been modified") {} + + WatchError(const WatchError &) = default; + WatchError& operator=(const WatchError &) = default; + + WatchError(WatchError &&) = default; + WatchError& operator=(WatchError &&) = default; + + virtual ~WatchError() = default; +}; + + +// MovedError and AskError are defined in shards.h +class MovedError; + +class AskError; + +void throw_error(redisContext &context, const std::string &err_info); + +void throw_error(const redisReply &reply); + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_ERRORS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/pipeline.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/pipeline.h new file mode 100644 index 000000000..52b01253f --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/pipeline.h @@ -0,0 +1,49 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_PIPELINE_H +#define SEWENEW_REDISPLUSPLUS_PIPELINE_H + +#include +#include +#include "connection.h" + +namespace sw { + +namespace redis { + +class PipelineImpl { +public: + template + void command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + } + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t /*cmd_num*/) { + // Reconnect to Redis to discard all commands. + connection.reconnect(); + } +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_PIPELINE_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.h new file mode 100644 index 000000000..71d975ee3 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.h @@ -0,0 +1,1844 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H + +#include +#include +#include +#include +#include "connection.h" +#include "utils.h" +#include "reply.h" +#include "command.h" +#include "redis.h" + +namespace sw { + +namespace redis { + +class QueuedReplies; + +// If any command throws, QueuedRedis resets the connection, and becomes invalid. +// In this case, the only thing we can do is to destory the QueuedRedis object. +template +class QueuedRedis { +public: + QueuedRedis(QueuedRedis &&) = default; + QueuedRedis& operator=(QueuedRedis &&) = default; + + // When it destructs, the underlying *Connection* will be closed, + // and any command that has NOT been executed will be ignored. + ~QueuedRedis() = default; + + Redis redis(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type; + + template + QueuedRedis& command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type; + + QueuedReplies exec(); + + void discard(); + + // CONNECTION commands. + + QueuedRedis& auth(const StringView &password) { + return command(cmd::auth, password); + } + + QueuedRedis& echo(const StringView &msg) { + return command(cmd::echo, msg); + } + + QueuedRedis& ping() { + return command(cmd::ping); + } + + QueuedRedis& ping(const StringView &msg) { + return command(cmd::ping, msg); + } + + // We DO NOT support the QUIT command. See *Redis::quit* doc for details. + // + // QueuedRedis& quit(); + + QueuedRedis& select(long long idx) { + return command(cmd::select, idx); + } + + QueuedRedis& swapdb(long long idx1, long long idx2) { + return command(cmd::swapdb, idx1, idx2); + } + + // SERVER commands. + + QueuedRedis& bgrewriteaof() { + return command(cmd::bgrewriteaof); + } + + QueuedRedis& bgsave() { + return command(cmd::bgsave); + } + + QueuedRedis& dbsize() { + return command(cmd::dbsize); + } + + QueuedRedis& flushall(bool async = false) { + return command(cmd::flushall, async); + } + + QueuedRedis& flushdb(bool async = false) { + return command(cmd::flushdb, async); + } + + QueuedRedis& info() { + return command(cmd::info); + } + + QueuedRedis& info(const StringView §ion) { + return command(cmd::info, section); + } + + QueuedRedis& lastsave() { + return command(cmd::lastsave); + } + + QueuedRedis& save() { + return command(cmd::save); + } + + // KEY commands. + + QueuedRedis& del(const StringView &key) { + return command(cmd::del, key); + } + + template + QueuedRedis& del(Input first, Input last) { + return command(cmd::del_range, first, last); + } + + template + QueuedRedis& del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + QueuedRedis& dump(const StringView &key) { + return command(cmd::dump, key); + } + + QueuedRedis& exists(const StringView &key) { + return command(cmd::exists, key); + } + + template + QueuedRedis& exists(Input first, Input last) { + return command(cmd::exists_range, first, last); + } + + template + QueuedRedis& exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + QueuedRedis& expire(const StringView &key, long long timeout) { + return command(cmd::expire, key, timeout); + } + + QueuedRedis& expire(const StringView &key, + const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); + } + + QueuedRedis& expireat(const StringView &key, long long timestamp) { + return command(cmd::expireat, key, timestamp); + } + + QueuedRedis& expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& keys(const StringView &pattern) { + return command(cmd::keys, pattern); + } + + QueuedRedis& move(const StringView &key, long long db) { + return command(cmd::move, key, db); + } + + QueuedRedis& persist(const StringView &key) { + return command(cmd::persist, key); + } + + QueuedRedis& pexpire(const StringView &key, long long timeout) { + return command(cmd::pexpire, key, timeout); + } + + QueuedRedis& pexpire(const StringView &key, + const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); + } + + QueuedRedis& pexpireat(const StringView &key, long long timestamp) { + return command(cmd::pexpireat, key, timestamp); + } + + QueuedRedis& pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& pttl(const StringView &key) { + return command(cmd::pttl, key); + } + + QueuedRedis& randomkey() { + return command(cmd::randomkey); + } + + QueuedRedis& rename(const StringView &key, const StringView &newkey) { + return command(cmd::rename, key, newkey); + } + + QueuedRedis& renamenx(const StringView &key, const StringView &newkey) { + return command(cmd::renamenx, key, newkey); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false) { + return command(cmd::restore, key, val, ttl, replace); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false) { + return restore(key, val, ttl.count(), replace); + } + + // TODO: sort + + QueuedRedis& scan(long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::scan, cursor, pattern, count); + } + + QueuedRedis& scan(long long cursor) { + return scan(cursor, "*", 10); + } + + QueuedRedis& scan(long long cursor, + const StringView &pattern) { + return scan(cursor, pattern, 10); + } + + QueuedRedis& scan(long long cursor, + long long count) { + return scan(cursor, "*", count); + } + + QueuedRedis& touch(const StringView &key) { + return command(cmd::touch, key); + } + + template + QueuedRedis& touch(Input first, Input last) { + return command(cmd::touch_range, first, last); + } + + template + QueuedRedis& touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + QueuedRedis& ttl(const StringView &key) { + return command(cmd::ttl, key); + } + + QueuedRedis& type(const StringView &key) { + return command(cmd::type, key); + } + + QueuedRedis& unlink(const StringView &key) { + return command(cmd::unlink, key); + } + + template + QueuedRedis& unlink(Input first, Input last) { + return command(cmd::unlink_range, first, last); + } + + template + QueuedRedis& unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + QueuedRedis& wait(long long numslaves, long long timeout) { + return command(cmd::wait, numslaves, timeout); + } + + QueuedRedis& wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); + } + + // STRING commands. + + QueuedRedis& append(const StringView &key, const StringView &str) { + return command(cmd::append, key, str); + } + + QueuedRedis& bitcount(const StringView &key, + long long start = 0, + long long end = -1) { + return command(cmd::bitcount, key, start, end); + } + + QueuedRedis& bitop(BitOp op, + const StringView &destination, + const StringView &key) { + return command(cmd::bitop, op, destination, key); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + Input first, + Input last) { + return command(cmd::bitop_range, op, destination, first, last); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + QueuedRedis& bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1) { + return command(cmd::bitpos, key, bit, start, end); + } + + QueuedRedis& decr(const StringView &key) { + return command(cmd::decr, key); + } + + QueuedRedis& decrby(const StringView &key, long long decrement) { + return command(cmd::decrby, key, decrement); + } + + QueuedRedis& get(const StringView &key) { + return command(cmd::get, key); + } + + QueuedRedis& getbit(const StringView &key, long long offset) { + return command(cmd::getbit, key, offset); + } + + QueuedRedis& getrange(const StringView &key, long long start, long long end) { + return command(cmd::getrange, key, start, end); + } + + QueuedRedis& getset(const StringView &key, const StringView &val) { + return command(cmd::getset, key, val); + } + + QueuedRedis& incr(const StringView &key) { + return command(cmd::incr, key); + } + + QueuedRedis& incrby(const StringView &key, long long increment) { + return command(cmd::incrby, key, increment); + } + + QueuedRedis& incrbyfloat(const StringView &key, double increment) { + return command(cmd::incrbyfloat, key, increment); + } + + template + QueuedRedis& mget(Input first, Input last) { + return command(cmd::mget, first, last); + } + + template + QueuedRedis& mget(std::initializer_list il) { + return mget(il.begin(), il.end()); + } + + template + QueuedRedis& mset(Input first, Input last) { + return command(cmd::mset, first, last); + } + + template + QueuedRedis& mset(std::initializer_list il) { + return mset(il.begin(), il.end()); + } + + template + QueuedRedis& msetnx(Input first, Input last) { + return command(cmd::msetnx, first, last); + } + + template + QueuedRedis& msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + QueuedRedis& psetex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::psetex, key, ttl, val); + } + + QueuedRedis& psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); + } + + QueuedRedis& set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS) { + _set_cmd_indexes.push_back(_cmd_num); + + return command(cmd::set, key, val, ttl.count(), type); + } + + QueuedRedis& setex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::setex, key, ttl, val); + } + + QueuedRedis& setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + return setex(key, ttl.count(), val); + } + + QueuedRedis& setnx(const StringView &key, const StringView &val) { + return command(cmd::setnx, key, val); + } + + QueuedRedis& setrange(const StringView &key, + long long offset, + const StringView &val) { + return command(cmd::setrange, key, offset, val); + } + + QueuedRedis& strlen(const StringView &key) { + return command(cmd::strlen, key); + } + + // LIST commands. + + QueuedRedis& blpop(const StringView &key, long long timeout) { + return command(cmd::blpop, key, timeout); + } + + QueuedRedis& blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(key, timeout.count()); + } + + template + QueuedRedis& blpop(Input first, Input last, long long timeout) { + return command(cmd::blpop_range, first, last, timeout); + } + + template + QueuedRedis& blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(first, last, timeout.count()); + } + + template + QueuedRedis& blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpop(const StringView &key, long long timeout) { + return command(cmd::brpop, key, timeout); + } + + QueuedRedis& brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(key, timeout.count()); + } + + template + QueuedRedis& brpop(Input first, Input last, long long timeout) { + return command(cmd::brpop_range, first, last, timeout); + } + + template + QueuedRedis& brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(first, last, timeout.count()); + } + + template + QueuedRedis& brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + long long timeout) { + return command(cmd::brpoplpush, source, destination, timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpoplpush(source, destination, timeout.count()); + } + + QueuedRedis& lindex(const StringView &key, long long index) { + return command(cmd::lindex, key, index); + } + + QueuedRedis& linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val) { + return command(cmd::linsert, key, position, pivot, val); + } + + QueuedRedis& llen(const StringView &key) { + return command(cmd::llen, key); + } + + QueuedRedis& lpop(const StringView &key) { + return command(cmd::lpop, key); + } + + QueuedRedis& lpush(const StringView &key, const StringView &val) { + return command(cmd::lpush, key, val); + } + + template + QueuedRedis& lpush(const StringView &key, Input first, Input last) { + return command(cmd::lpush_range, key, first, last); + } + + template + QueuedRedis& lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + QueuedRedis& lpushx(const StringView &key, const StringView &val) { + return command(cmd::lpushx, key, val); + } + + QueuedRedis& lrange(const StringView &key, + long long start, + long long stop) { + return command(cmd::lrange, key, start, stop); + } + + QueuedRedis& lrem(const StringView &key, long long count, const StringView &val) { + return command(cmd::lrem, key, count, val); + } + + QueuedRedis& lset(const StringView &key, long long index, const StringView &val) { + return command(cmd::lset, key, index, val); + } + + QueuedRedis& ltrim(const StringView &key, long long start, long long stop) { + return command(cmd::ltrim, key, start, stop); + } + + QueuedRedis& rpop(const StringView &key) { + return command(cmd::rpop, key); + } + + QueuedRedis& rpoplpush(const StringView &source, const StringView &destination) { + return command(cmd::rpoplpush, source, destination); + } + + QueuedRedis& rpush(const StringView &key, const StringView &val) { + return command(cmd::rpush, key, val); + } + + template + QueuedRedis& rpush(const StringView &key, Input first, Input last) { + return command(cmd::rpush_range, key, first, last); + } + + template + QueuedRedis& rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + QueuedRedis& rpushx(const StringView &key, const StringView &val) { + return command(cmd::rpushx, key, val); + } + + // HASH commands. + + QueuedRedis& hdel(const StringView &key, const StringView &field) { + return command(cmd::hdel, key, field); + } + + template + QueuedRedis& hdel(const StringView &key, Input first, Input last) { + return command(cmd::hdel_range, key, first, last); + } + + template + QueuedRedis& hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + QueuedRedis& hexists(const StringView &key, const StringView &field) { + return command(cmd::hexists, key, field); + } + + QueuedRedis& hget(const StringView &key, const StringView &field) { + return command(cmd::hget, key, field); + } + + QueuedRedis& hgetall(const StringView &key) { + return command(cmd::hgetall, key); + } + + QueuedRedis& hincrby(const StringView &key, + const StringView &field, + long long increment) { + return command(cmd::hincrby, key, field, increment); + } + + QueuedRedis& hincrbyfloat(const StringView &key, + const StringView &field, + double increment) { + return command(cmd::hincrbyfloat, key, field, increment); + } + + QueuedRedis& hkeys(const StringView &key) { + return command(cmd::hkeys, key); + } + + QueuedRedis& hlen(const StringView &key) { + return command(cmd::hlen, key); + } + + template + QueuedRedis& hmget(const StringView &key, Input first, Input last) { + return command(cmd::hmget, key, first, last); + } + + template + QueuedRedis& hmget(const StringView &key, std::initializer_list il) { + return hmget(key, il.begin(), il.end()); + } + + template + QueuedRedis& hmset(const StringView &key, Input first, Input last) { + return command(cmd::hmset, key, first, last); + } + + template + QueuedRedis& hmset(const StringView &key, std::initializer_list il) { + return hmset(key, il.begin(), il.end()); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::hscan, key, cursor, pattern, count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return hscan(key, cursor, pattern, 10); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + long long count) { + return hscan(key, cursor, "*", count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor) { + return hscan(key, cursor, "*", 10); + } + + QueuedRedis& hset(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hset, key, field, val); + } + + QueuedRedis& hset(const StringView &key, const std::pair &item) { + return hset(key, item.first, item.second); + } + + QueuedRedis& hsetnx(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hsetnx, key, field, val); + } + + QueuedRedis& hsetnx(const StringView &key, const std::pair &item) { + return hsetnx(key, item.first, item.second); + } + + QueuedRedis& hstrlen(const StringView &key, const StringView &field) { + return command(cmd::hstrlen, key, field); + } + + QueuedRedis& hvals(const StringView &key) { + return command(cmd::hvals, key); + } + + // SET commands. + + QueuedRedis& sadd(const StringView &key, const StringView &member) { + return command(cmd::sadd, key, member); + } + + template + QueuedRedis& sadd(const StringView &key, Input first, Input last) { + return command(cmd::sadd_range, key, first, last); + } + + template + QueuedRedis& sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + QueuedRedis& scard(const StringView &key) { + return command(cmd::scard, key); + } + + template + QueuedRedis& sdiff(Input first, Input last) { + return command(cmd::sdiff, first, last); + } + + template + QueuedRedis& sdiff(std::initializer_list il) { + return sdiff(il.begin(), il.end()); + } + + QueuedRedis& sdiffstore(const StringView &destination, const StringView &key) { + return command(cmd::sdiffstore, destination, key); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, + Input first, + Input last) { + return command(cmd::sdiffstore_range, destination, first, last); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + QueuedRedis& sinter(Input first, Input last) { + return command(cmd::sinter, first, last); + } + + template + QueuedRedis& sinter(std::initializer_list il) { + return sinter(il.begin(), il.end()); + } + + QueuedRedis& sinterstore(const StringView &destination, const StringView &key) { + return command(cmd::sinterstore, destination, key); + } + + template + QueuedRedis& sinterstore(const StringView &destination, + Input first, + Input last) { + return command(cmd::sinterstore_range, destination, first, last); + } + + template + QueuedRedis& sinterstore(const StringView &destination, std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + QueuedRedis& sismember(const StringView &key, const StringView &member) { + return command(cmd::sismember, key, member); + } + + QueuedRedis& smembers(const StringView &key) { + return command(cmd::smembers, key); + } + + QueuedRedis& smove(const StringView &source, + const StringView &destination, + const StringView &member) { + return command(cmd::smove, source, destination, member); + } + + QueuedRedis& spop(const StringView &key) { + return command(cmd::spop, key); + } + + QueuedRedis& spop(const StringView &key, long long count) { + return command(cmd::spop_range, key, count); + } + + QueuedRedis& srandmember(const StringView &key) { + return command(cmd::srandmember, key); + } + + QueuedRedis& srandmember(const StringView &key, long long count) { + return command(cmd::srandmember_range, key, count); + } + + QueuedRedis& srem(const StringView &key, const StringView &member) { + return command(cmd::srem, key, member); + } + + template + QueuedRedis& srem(const StringView &key, Input first, Input last) { + return command(cmd::srem_range, key, first, last); + } + + template + QueuedRedis& srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::sscan, key, cursor, pattern, count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return sscan(key, cursor, pattern, 10); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + long long count) { + return sscan(key, cursor, "*", count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor) { + return sscan(key, cursor, "*", 10); + } + + template + QueuedRedis& sunion(Input first, Input last) { + return command(cmd::sunion, first, last); + } + + template + QueuedRedis& sunion(std::initializer_list il) { + return sunion(il.begin(), il.end()); + } + + QueuedRedis& sunionstore(const StringView &destination, const StringView &key) { + return command(cmd::sunionstore, destination, key); + } + + template + QueuedRedis& sunionstore(const StringView &destination, Input first, Input last) { + return command(cmd::sunionstore_range, destination, first, last); + } + + template + QueuedRedis& sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + QueuedRedis& bzpopmax(const StringView &key, long long timeout) { + return command(cmd::bzpopmax, key, timeout); + } + + QueuedRedis& bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(key, timeout.count()); + } + + template + QueuedRedis& bzpopmax(Input first, Input last, long long timeout) { + return command(cmd::bzpopmax_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, long long timeout) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, long long timeout) { + return command(cmd::bzpopmin, key, timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(key, timeout.count()); + } + + template + QueuedRedis& bzpopmin(Input first, Input last, long long timeout) { + return command(cmd::bzpopmin_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, long long timeout) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + QueuedRedis& zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return command(cmd::zadd, key, member, score, type, changed); + } + + template + QueuedRedis& zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return command(cmd::zadd_range, key, first, last, type, changed); + } + + QueuedRedis& zcard(const StringView &key) { + return command(cmd::zcard, key); + } + + template + QueuedRedis& zcount(const StringView &key, const Interval &interval) { + return command(cmd::zcount, key, interval); + } + + QueuedRedis& zincrby(const StringView &key, double increment, const StringView &member) { + return command(cmd::zincrby, key, increment, member); + } + + QueuedRedis& zinterstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zinterstore, destination, key, weight); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + return command(cmd::zinterstore_range, destination, first, last, type); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + QueuedRedis& zlexcount(const StringView &key, const Interval &interval) { + return command(cmd::zlexcount, key, interval); + } + + QueuedRedis& zpopmax(const StringView &key) { + return command(cmd::zpopmax, key, 1); + } + + QueuedRedis& zpopmax(const StringView &key, long long count) { + return command(cmd::zpopmax, key, count); + } + + QueuedRedis& zpopmin(const StringView &key) { + return command(cmd::zpopmin, key, 1); + } + + QueuedRedis& zpopmin(const StringView &key, long long count) { + return command(cmd::zpopmin, key, count); + } + + // NOTE: *QueuedRedis::zrange*'s parameters are different from *Redis::zrange*. + // *Redis::zrange* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::zrange*. So we have to use an extra parameter: *with_scores*, + // to decide whether we should send *WITHSCORES* option to Redis. This also applies to + // other commands with the *WITHSCORES* option, e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, + // *ZREVRANGEBYSCORE*. + QueuedRedis& zrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrangebylex(const StringView &key, const Interval &interval) { + return zrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrank(const StringView &key, const StringView &member) { + return command(cmd::zrank, key, member); + } + + QueuedRedis& zrem(const StringView &key, const StringView &member) { + return command(cmd::zrem, key, member); + } + + template + QueuedRedis& zrem(const StringView &key, Input first, Input last) { + return command(cmd::zrem_range, key, first, last); + } + + template + QueuedRedis& zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + QueuedRedis& zremrangebylex(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebylex, key, interval); + } + + QueuedRedis& zremrangebyrank(const StringView &key, long long start, long long stop) { + return command(cmd::zremrangebyrank, key, start, stop); + } + + template + QueuedRedis& zremrangebyscore(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebyscore, key, interval); + } + + // See comments on *ZRANGE*. + QueuedRedis& zrevrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrevrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrevrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, const Interval &interval) { + return zrevrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrevrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrevrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrevrank(const StringView &key, const StringView &member) { + return command(cmd::zrevrank, key, member); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::zscan, key, cursor, pattern, count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return zscan(key, cursor, pattern, 10); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + long long count) { + return zscan(key, cursor, "*", count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor) { + return zscan(key, cursor, "*", 10); + } + + QueuedRedis& zscore(const StringView &key, const StringView &member) { + return command(cmd::zscore, key, member); + } + + QueuedRedis& zunionstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zunionstore, destination, key, weight); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + return command(cmd::zunionstore_range, destination, first, last, type); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + QueuedRedis& pfadd(const StringView &key, const StringView &element) { + return command(cmd::pfadd, key, element); + } + + template + QueuedRedis& pfadd(const StringView &key, Input first, Input last) { + return command(cmd::pfadd_range, key, first, last); + } + + template + QueuedRedis& pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + QueuedRedis& pfcount(const StringView &key) { + return command(cmd::pfcount, key); + } + + template + QueuedRedis& pfcount(Input first, Input last) { + return command(cmd::pfcount_range, first, last); + } + + template + QueuedRedis& pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + QueuedRedis& pfmerge(const StringView &destination, const StringView &key) { + return command(cmd::pfmerge, destination, key); + } + + template + QueuedRedis& pfmerge(const StringView &destination, Input first, Input last) { + return command(cmd::pfmerge_range, destination, first, last); + } + + template + QueuedRedis& pfmerge(const StringView &destination, std::initializer_list il) { + return pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + QueuedRedis& geoadd(const StringView &key, + const std::tuple &member) { + return command(cmd::geoadd, key, member); + } + + template + QueuedRedis& geoadd(const StringView &key, + Input first, + Input last) { + return command(cmd::geoadd_range, key, first, last); + } + + template + QueuedRedis& geoadd(const StringView &key, std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + QueuedRedis& geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M) { + return command(cmd::geodist, key, member1, member2, unit); + } + + template + QueuedRedis& geohash(const StringView &key, Input first, Input last) { + return command(cmd::geohash_range, key, first, last); + } + + template + QueuedRedis& geohash(const StringView &key, std::initializer_list il) { + return geohash(key, il.begin(), il.end()); + } + + template + QueuedRedis& geopos(const StringView &key, Input first, Input last) { + return command(cmd::geopos_range, key, first, last); + } + + template + QueuedRedis& geopos(const StringView &key, std::initializer_list il) { + return geopos(key, il.begin(), il.end()); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _georadius_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadius_store, + key, + loc, + radius, + unit, + destination, + store_dist, + count); + } + + // NOTE: *QueuedRedis::georadius*'s parameters are different from *Redis::georadius*. + // *Redis::georadius* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::georadius*. So we have to use extra parameters to decide + // whether we should send options to Redis. This also applies to *GEORADIUSBYMEMBER*. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _georadius_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + destination, + store_dist, + count); + } + + // See the comments on *GEORADIUS*. + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + // SCRIPTING commands. + + QueuedRedis& eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return command(cmd::eval, script, keys, args); + } + + QueuedRedis& evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return command(cmd::evalsha, script, keys, args); + } + + template + QueuedRedis& script_exists(Input first, Input last) { + return command(cmd::script_exists_range, first, last); + } + + template + QueuedRedis& script_exists(std::initializer_list il) { + return script_exists(il.begin(), il.end()); + } + + QueuedRedis& script_flush() { + return command(cmd::script_flush); + } + + QueuedRedis& script_kill() { + return command(cmd::script_kill); + } + + QueuedRedis& script_load(const StringView &script) { + return command(cmd::script_load, script); + } + + // PUBSUB commands. + + QueuedRedis& publish(const StringView &channel, const StringView &message) { + return command(cmd::publish, channel, message); + } + + // Stream commands. + + QueuedRedis& xack(const StringView &key, const StringView &group, const StringView &id) { + return command(cmd::xack, key, group, id); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, Input first, Input last) { + return command(cmd::xack_range, key, group, first, last); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, Input first, Input last) { + return command(cmd::xadd_range, key, id, first, last); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true) { + return command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id) { + return command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last) { + return command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il) { + return xclaim(key, group, consumer, min_idle_time, il.begin(), il.end()); + } + + QueuedRedis& xdel(const StringView &key, const StringView &id) { + return command(cmd::xdel, key, id); + } + + template + QueuedRedis& xdel(const StringView &key, Input first, Input last) { + return command(cmd::xdel_range, key, first, last); + } + + template + QueuedRedis& xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + QueuedRedis& xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false) { + return command(cmd::xgroup_create, key, group, id, mkstream); + } + + QueuedRedis& xgroup_setid(const StringView &key, + const StringView &group, + const StringView &id) { + return command(cmd::xgroup_setid, key, group, id); + } + + QueuedRedis& xgroup_destroy(const StringView &key, const StringView &group) { + return command(cmd::xgroup_destroy, key, group); + } + + QueuedRedis& xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer) { + return command(cmd::xgroup_delconsumer, key, group, consumer); + } + + QueuedRedis& xlen(const StringView &key) { + return command(cmd::xlen, key); + } + + QueuedRedis& xpending(const StringView &key, const StringView &group) { + return command(cmd::xpending, key, group); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xpending_detail, key, group, start, end, count); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + return command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end) { + return command(cmd::xrange, key, start, end); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xrange, key, start, end, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id, long long count) { + return command(cmd::xread, key, id, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id) { + return xread(key, id, 0); + } + + template + auto xread(Input first, Input last, long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xread_range, first, last, count); + } + + template + auto xread(Input first, Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, 0); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return command(cmd::xread_block, key, id, timeout.count(), count); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xread(key, id, timeout, 0); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xread_block_range, first, last, timeout.count(), count); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, timeout, 0); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + return command(cmd::xreadgroup, group, consumer, key, id, count, noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count) { + return xreadgroup(group, consumer, key, id, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, 0, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) { + return command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return xreadgroup(group, consumer, key, id, timeout, count, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xreadgroup(group, consumer, key, id, timeout, 0, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, 0, false); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start) { + return command(cmd::xrevrange, key, end, start); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + return command(cmd::xrevrange, key, end, start, count); + } + + QueuedRedis& xtrim(const StringView &key, long long count, bool approx = true) { + return command(cmd::xtrim, key, count, approx); + } + +private: + friend class Redis; + + friend class RedisCluster; + + template + QueuedRedis(const ConnectionSPtr &connection, Args &&...args); + + void _sanity_check() const; + + void _reset(); + + void _invalidate(); + + void _rewrite_replies(std::vector &replies) const; + + template + void _rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const; + + ConnectionSPtr _connection; + + Impl _impl; + + std::size_t _cmd_num = 0; + + std::vector _set_cmd_indexes; + + std::vector _georadius_cmd_indexes; + + bool _valid = true; +}; + +class QueuedReplies { +public: + std::size_t size() const; + + redisReply& get(std::size_t idx); + + template + Result get(std::size_t idx); + + template + void get(std::size_t idx, Output output); + +private: + template + friend class QueuedRedis; + + explicit QueuedReplies(std::vector replies) : _replies(std::move(replies)) {} + + void _index_check(std::size_t idx) const; + + std::vector _replies; +}; + +} + +} + +#include "queued_redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.hpp b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.hpp new file mode 100644 index 000000000..409f48aca --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.hpp @@ -0,0 +1,208 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP + +namespace sw { + +namespace redis { + +template +template +QueuedRedis::QueuedRedis(const ConnectionSPtr &connection, Args &&...args) : + _connection(connection), + _impl(std::forward(args)...) { + assert(_connection); +} + +template +Redis QueuedRedis::redis() { + return Redis(_connection); +} + +template +template +auto QueuedRedis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type { + try { + _sanity_check(); + + _impl.command(*_connection, cmd, std::forward(args)...); + + ++_cmd_num; + } catch (const Error &e) { + _invalidate(); + throw; + } + + return *this; +} + +template +template +QueuedRedis& QueuedRedis::command(const StringView &cmd_name, Args &&...args) { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +template +auto QueuedRedis::command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type { + if (first == last) { + throw Error("command: empty range"); + } + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +QueuedReplies QueuedRedis::exec() { + try { + _sanity_check(); + + auto replies = _impl.exec(*_connection, _cmd_num); + + _rewrite_replies(replies); + + _reset(); + + return QueuedReplies(std::move(replies)); + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +void QueuedRedis::discard() { + try { + _sanity_check(); + + _impl.discard(*_connection, _cmd_num); + + _reset(); + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +void QueuedRedis::_sanity_check() const { + if (!_valid) { + throw Error("Not in valid state"); + } + + if (_connection->broken()) { + throw Error("Connection is broken"); + } +} + +template +inline void QueuedRedis::_reset() { + _cmd_num = 0; + + _set_cmd_indexes.clear(); + + _georadius_cmd_indexes.clear(); +} + +template +void QueuedRedis::_invalidate() { + _valid = false; + + _reset(); +} + +template +void QueuedRedis::_rewrite_replies(std::vector &replies) const { + _rewrite_replies(_set_cmd_indexes, reply::rewrite_set_reply, replies); + + _rewrite_replies(_georadius_cmd_indexes, reply::rewrite_georadius_reply, replies); +} + +template +template +void QueuedRedis::_rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const { + for (auto idx : indexes) { + assert(idx < replies.size()); + + auto &reply = replies[idx]; + + assert(reply); + + rewriter(*reply); + } +} + +inline std::size_t QueuedReplies::size() const { + return _replies.size(); +} + +inline redisReply& QueuedReplies::get(std::size_t idx) { + _index_check(idx); + + auto &reply = _replies[idx]; + + assert(reply); + + return *reply; +} + +template +inline Result QueuedReplies::get(std::size_t idx) { + auto &reply = get(idx); + + return reply::parse(reply); +} + +template +inline void QueuedReplies::get(std::size_t idx, Output output) { + auto &reply = get(idx); + + reply::to_array(reply, output); +} + +inline void QueuedReplies::_index_check(std::size_t idx) const { + if (idx >= size()) { + throw Error("Out of range"); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis++.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis++.h new file mode 100644 index 000000000..0da0ebb16 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis++.h @@ -0,0 +1,25 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H +#define SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H + +#include "redis.h" +#include "redis_cluster.h" +#include "queued_redis.h" +#include "sentinel.h" + +#endif // end SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.h new file mode 100644 index 000000000..b54afb96b --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.h @@ -0,0 +1,1523 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_H +#define SEWENEW_REDISPLUSPLUS_REDIS_H + +#include +#include +#include +#include +#include +#include "connection_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class Redis { +public: + Redis(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : _pool(pool_opts, connection_opts) {} + + // Construct Redis instance with URI: + // "tcp://127.0.0.1", "tcp://127.0.0.1:6379", or "unix://path/to/socket" + explicit Redis(const std::string &uri); + + Redis(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role, + const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(SimpleSentinel(sentinel, master_name, role), pool_opts, connection_opts) {} + + Redis(const Redis &) = delete; + Redis& operator=(const Redis &) = delete; + + Redis(Redis &&) = default; + Redis& operator=(Redis &&) = default; + + Pipeline pipeline(); + + Transaction transaction(bool piped = false); + + Subscriber subscriber(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type; + + template + Result command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // CONNECTION commands. + + void auth(const StringView &password); + + std::string echo(const StringView &msg); + + std::string ping(); + + std::string ping(const StringView &msg); + + // After sending QUIT, only the current connection will be close, while + // other connections in the pool is still open. This is a strange behavior. + // So we DO NOT support the QUIT command. If you want to quit connection to + // server, just destroy the Redis object. + // + // void quit(); + + // We get a connection from the pool, and send the SELECT command to switch + // to a specified DB. However, when we try to send other commands to the + // given DB, we might get a different connection from the pool, and these + // commands, in fact, work on other DB. e.g. + // + // redis.select(1); // get a connection from the pool and switch to the 1th DB + // redis.get("key"); // might get another connection from the pool, + // // and try to get 'key' on the default DB + // + // Obviously, this is NOT what we expect. So we DO NOT support SELECT command. + // In order to select a DB, we can specify the DB index with the ConnectionOptions. + // + // However, since Pipeline and Transaction always send multiple commands on a + // single connection, these two classes have a *select* method. + // + // void select(long long idx); + + void swapdb(long long idx1, long long idx2); + + // SERVER commands. + + void bgrewriteaof(); + + void bgsave(); + + long long dbsize(); + + void flushall(bool async = false); + + void flushdb(bool async = false); + + std::string info(); + + std::string info(const StringView §ion); + + long long lastsave(); + + void save(); + + // KEY commands. + + long long del(const StringView &key); + + template + long long del(Input first, Input last); + + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + OptionalString dump(const StringView &key); + + long long exists(const StringView &key); + + template + long long exists(Input first, Input last); + + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + bool expire(const StringView &key, long long timeout); + + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + bool expireat(const StringView &key, long long timestamp); + + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + template + void keys(const StringView &pattern, Output output); + + bool move(const StringView &key, long long db); + + bool persist(const StringView &key); + + bool pexpire(const StringView &key, long long timeout); + + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + bool pexpireat(const StringView &key, long long timestamp); + + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + long long pttl(const StringView &key); + + OptionalString randomkey(); + + void rename(const StringView &key, const StringView &newkey); + + bool renamenx(const StringView &key, const StringView &newkey); + + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + template + long long scan(long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long scan(long long cursor, + Output output); + + template + long long scan(long long cursor, + const StringView &pattern, + Output output); + + template + long long scan(long long cursor, + long long count, + Output output); + + long long touch(const StringView &key); + + template + long long touch(Input first, Input last); + + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + long long ttl(const StringView &key); + + std::string type(const StringView &key); + + long long unlink(const StringView &key); + + template + long long unlink(Input first, Input last); + + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + long long wait(long long numslaves, long long timeout); + + long long wait(long long numslaves, const std::chrono::milliseconds &timeout); + + // STRING commands. + + long long append(const StringView &key, const StringView &str); + + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + long long decr(const StringView &key); + + long long decrby(const StringView &key, long long decrement); + + OptionalString get(const StringView &key); + + long long getbit(const StringView &key, long long offset); + + std::string getrange(const StringView &key, long long start, long long end); + + OptionalString getset(const StringView &key, const StringView &val); + + long long incr(const StringView &key); + + long long incrby(const StringView &key, long long increment); + + double incrbyfloat(const StringView &key, double increment); + + template + void mget(Input first, Input last, Output output); + + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + template + void mset(Input first, Input last); + + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + template + bool msetnx(Input first, Input last); + + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + void setex(const StringView &key, + long long ttl, + const StringView &val); + + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + bool setnx(const StringView &key, const StringView &val); + + long long setrange(const StringView &key, long long offset, const StringView &val); + + long long strlen(const StringView &key); + + // LIST commands. + + OptionalStringPair blpop(const StringView &key, long long timeout); + + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + OptionalStringPair brpop(const StringView &key, long long timeout); + + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + OptionalString lindex(const StringView &key, long long index); + + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + long long llen(const StringView &key); + + OptionalString lpop(const StringView &key); + + long long lpush(const StringView &key, const StringView &val); + + template + long long lpush(const StringView &key, Input first, Input last); + + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + long long lpushx(const StringView &key, const StringView &val); + + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + long long lrem(const StringView &key, long long count, const StringView &val); + + void lset(const StringView &key, long long index, const StringView &val); + + void ltrim(const StringView &key, long long start, long long stop); + + OptionalString rpop(const StringView &key); + + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + long long rpush(const StringView &key, const StringView &val); + + template + long long rpush(const StringView &key, Input first, Input last); + + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + long long hdel(const StringView &key, const StringView &field); + + template + long long hdel(const StringView &key, Input first, Input last); + + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + bool hexists(const StringView &key, const StringView &field); + + OptionalString hget(const StringView &key, const StringView &field); + + template + void hgetall(const StringView &key, Output output); + + long long hincrby(const StringView &key, const StringView &field, long long increment); + + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + template + void hkeys(const StringView &key, Output output); + + long long hlen(const StringView &key); + + template + void hmget(const StringView &key, Input first, Input last, Output output); + + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + template + void hmset(const StringView &key, Input first, Input last); + + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + bool hset(const StringView &key, const StringView &field, const StringView &val); + + bool hset(const StringView &key, const std::pair &item); + + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + bool hsetnx(const StringView &key, const std::pair &item); + + long long hstrlen(const StringView &key, const StringView &field); + + template + void hvals(const StringView &key, Output output); + + // SET commands. + + long long sadd(const StringView &key, const StringView &member); + + template + long long sadd(const StringView &key, Input first, Input last); + + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + long long scard(const StringView &key); + + template + void sdiff(Input first, Input last, Output output); + + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + long long sdiffstore(const StringView &destination, const StringView &key); + + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + void sinter(Input first, Input last, Output output); + + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + long long sinterstore(const StringView &destination, const StringView &key); + + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + bool sismember(const StringView &key, const StringView &member); + + template + void smembers(const StringView &key, Output output); + + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + OptionalString spop(const StringView &key); + + template + void spop(const StringView &key, long long count, Output output); + + OptionalString srandmember(const StringView &key); + + template + void srandmember(const StringView &key, long long count, Output output); + + long long srem(const StringView &key, const StringView &member); + + template + long long srem(const StringView &key, Input first, Input last); + + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + template + void sunion(Input first, Input last, Output output); + + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + long long sunionstore(const StringView &destination, const StringView &key); + + template + long long sunionstore(const StringView &destination, Input first, Input last); + + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + long long zcard(const StringView &key); + + template + long long zcount(const StringView &key, const Interval &interval); + + double zincrby(const StringView &key, double increment, const StringView &member); + + // There's no aggregation type parameter for single key overload, since these 3 types + // have the same effect. + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + // If *Input* is an iterator of a container of string, + // we use the default weight, i.e. 1, and send + // *ZINTERSTORE destination numkeys key [key ...] [AGGREGATE SUM|MIN|MAX]* command. + // If *Input* is an iterator of a container of pair, i.e. key-weight pair, + // we send the command with the given weights: + // *ZINTERSTORE destination numkeys key [key ...] + // [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]* + // + // The following code use the default weight: + // + // vector keys = {"k1", "k2", "k3"}; + // redis.zinterstore(destination, keys.begin(), keys.end()); + // + // On the other hand, the following code use the given weights: + // + // vector> keys_with_weights = {{"k1", 1}, {"k2", 2}, {"k3", 3}}; + // redis.zinterstore(destination, keys_with_weights.begin(), keys_with_weights.end()); + // + // NOTE: `keys_with_weights` can also be of type `unordered_map`. + // However, it will be slower than vector>, since we use + // `distance(first, last)` to calculate the *numkeys* parameter. + // + // This also applies to *ZUNIONSTORE* command. + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + long long zlexcount(const StringView &key, const Interval &interval); + + Optional> zpopmax(const StringView &key); + + template + void zpopmax(const StringView &key, long long count, Output output); + + Optional> zpopmin(const StringView &key); + + template + void zpopmin(const StringView &key, long long count, Output output); + + // If *output* is an iterator of a container of string, + // we send *ZRANGE key start stop* command. + // If it's an iterator of a container of pair, + // we send *ZRANGE key start stop WITHSCORES* command. + // + // The following code sends *ZRANGE* without the *WITHSCORES* option: + // + // vector result; + // redis.zrange("key", 0, -1, back_inserter(result)); + // + // On the other hand, the following code sends command with *WITHSCORES* option: + // + // unordered_map with_score; + // redis.zrange("key", 0, -1, inserter(with_score, with_score.end())); + // + // This also applies to other commands with the *WITHSCORES* option, + // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrank(const StringView &key, const StringView &member); + + long long zrem(const StringView &key, const StringView &member); + + template + long long zrem(const StringView &key, Input first, Input last); + + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + OptionalDouble zscore(const StringView &key, const StringView &member); + + // There's no aggregation type parameter for single key overload, since these 3 types + // have the same effect. + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + // See *zinterstore* comment for how to use this method. + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + bool pfadd(const StringView &key, const StringView &element); + + template + bool pfadd(const StringView &key, Input first, Input last); + + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + void script_exists(Input first, Input last, Output output); + + template + void script_exists(std::initializer_list il, Output output) { + script_exists(il.begin(), il.end(), output); + } + + void script_flush(); + + void script_kill(); + + std::string script_load(const StringView &script); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Transaction commands. + void watch(const StringView &key); + + template + void watch(Input first, Input last); + + template + void watch(std::initializer_list il) { + watch(il.begin(), il.end()); + } + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first ,last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + class ConnectionPoolGuard { + public: + ConnectionPoolGuard(ConnectionPool &pool, + Connection &connection) : _pool(pool), _connection(connection) {} + + ~ConnectionPoolGuard() { + _pool.release(std::move(_connection)); + } + + private: + ConnectionPool &_pool; + Connection &_connection; + }; + + template + friend class QueuedRedis; + + friend class RedisCluster; + + // For internal use. + explicit Redis(const ConnectionSPtr &connection); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _command(Connection &connection, Cmd cmd, Args &&...args); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + // Pool Mode. + // Public constructors create a *Redis* instance with a pool. + // In this case, *_connection* is a null pointer, and is never used. + ConnectionPool _pool; + + // Single Connection Mode. + // Private constructor creats a *Redis* instance with a single connection. + // This is used when we create Transaction, Pipeline and Subscriber. + // In this case, *_pool* is empty, and is never used. + ConnectionSPtr _connection; +}; + +} + +} + +#include "redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.hpp b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.hpp new file mode 100644 index 000000000..3a227a6f1 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.hpp @@ -0,0 +1,1365 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_HPP + +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +template +auto Redis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (_connection) { + // Single Connection Mode. + // TODO: In this case, should we reconnect? + if (_connection->broken()) { + throw Error("Connection is broken"); + } + + return _command(*_connection, cmd, std::forward(args)...); + } else { + // Pool Mode, i.e. get connection from pool. + auto connection = _pool.fetch(); + + assert(!connection.broken()); + + ConnectionPoolGuard guard(_pool, connection); + + return _command(connection, cmd, std::forward(args)...); + } +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, ReplyUPtr>::type { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (first == last) { + throw Error("command: empty range"); + } + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +Result Redis::command(const StringView &cmd_name, Args &&...args) { + auto r = command(cmd_name, std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long Redis::del(Input first, Input last) { + if (first == last) { + throw Error("DEL: no key specified"); + } + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::exists(Input first, Input last) { + if (first == last) { + throw Error("EXISTS: no key specified"); + } + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool Redis::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool Redis::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +template +void Redis::keys(const StringView &pattern, Output output) { + auto reply = command(cmd::keys, pattern); + + reply::to_array(*reply, output); +} + +inline bool Redis::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool Redis::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void Redis::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long Redis::scan(long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::scan, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::scan(long long cursor, + const StringView &pattern, + Output output) { + return scan(cursor, pattern, 10, output); +} + +template +inline long long Redis::scan(long long cursor, + long long count, + Output output) { + return scan(cursor, "*", count, output); +} + +template +inline long long Redis::scan(long long cursor, + Output output) { + return scan(cursor, "*", 10, output); +} + +template +long long Redis::touch(Input first, Input last) { + if (first == last) { + throw Error("TOUCH: no key specified"); + } + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::unlink(Input first, Input last) { + if (first == last) { + throw Error("UNLINK: no key specified"); + } + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +inline long long Redis::wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); +} + +// STRING commands. + +template +long long Redis::bitop(BitOp op, const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("BITOP: no key specified"); + } + + auto reply = command(cmd::bitop_range, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::mget(Input first, Input last, Output output) { + if (first == last) { + throw Error("MGET: no key specified"); + } + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::mset(Input first, Input last) { + if (first == last) { + throw Error("MSET: no key specified"); + } + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool Redis::msetnx(Input first, Input last) { + if (first == last) { + throw Error("MSETNX: no key specified"); + } + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void Redis::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void Redis::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair Redis::blpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BLPOP: no key specified"); + } + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair Redis::brpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BRPOP: no key specified"); + } + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString Redis::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long Redis::lpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("LPUSH: no key specified"); + } + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long Redis::rpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("RPUSH: no key specified"); + } + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long Redis::hdel(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HDEL: no key specified"); + } + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmget(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("HMGET: no key specified"); + } + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmset(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HMSET: no key specified"); + } + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +inline void Redis::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long Redis::sadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SADD: no key specified"); + } + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sdiff(Input first, Input last, Output output) { + if (first == last) { + throw Error("SDIFF: no key specified"); + } + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sdiffstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SDIFFSTORE: no key specified"); + } + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sinter(Input first, Input last, Output output) { + if (first == last) { + throw Error("SINTER: no key specified"); + } + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sinterstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SINTERSTORE: no key specified"); + } + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void Redis::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long Redis::srem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SREM: no key specified"); + } + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void Redis::sunion(Input first, Input last, Output output) { + if (first == last) { + throw Error("SUNION: no key specified"); + } + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sunionstore(const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("SUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto Redis::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto Redis::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto Redis::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto Redis::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long Redis::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + if (first == last) { + throw Error("ZADD: no key specified"); + } + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long Redis::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZINTERSTORE: no key specified"); + } + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long Redis::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void Redis::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zrem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("ZREM: no key specified"); + } + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long Redis::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool Redis::pfadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("PFADD: no key specified"); + } + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::pfcount(Input first, Input last) { + if (first == last) { + throw Error("PFCOUNT: no key specified"); + } + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void Redis::pfmerge(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("PFMERGE: no key specified"); + } + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long Redis::geoadd(const StringView &key, + Input first, + Input last) { + if (first == last) { + throw Error("GEOADD: no key specified"); + } + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::geohash(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOHASH: no key specified"); + } + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::geopos(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOPOS: no key specified"); + } + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void Redis::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + auto reply = command(cmd::eval, script, keys, args); + + return reply::parse(*reply); +} + +template +void Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + auto reply = command(cmd::eval, script, keys, args); + + reply::to_array(*reply, output); +} + +template +Result Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + auto reply = command(cmd::evalsha, script, keys, args); + + return reply::parse(*reply); +} + +template +void Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + auto reply = command(cmd::evalsha, script, keys, args); + + reply::to_array(*reply, output); +} + +template +void Redis::script_exists(Input first, Input last, Output output) { + if (first == last) { + throw Error("SCRIPT EXISTS: no key specified"); + } + + auto reply = command(cmd::script_exists_range, first, last); + + reply::to_array(*reply, output); +} + +// Transaction commands. + +template +void Redis::watch(Input first, Input last) { + auto reply = command(cmd::watch_range, first, last); + + reply::parse(*reply); +} + +// Stream commands. + +template +long long Redis::xack(const StringView &key, const StringView &group, Input first, Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, const StringView &id, Input first, Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long Redis::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto Redis::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +ReplyUPtr Redis::_command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + auto reply = connection.recv(); + + return reply; +} + +template +inline ReplyUPtr Redis::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr Redis::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr Redis::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_HPP diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.h new file mode 100644 index 000000000..50a221367 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.h @@ -0,0 +1,1395 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H + +#include +#include +#include +#include +#include "shards_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "redis.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class RedisCluster { +public: + RedisCluster(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(pool_opts, connection_opts) {} + + // Construct RedisCluster with URI: + // "tcp://127.0.0.1" or "tcp://127.0.0.1:6379" + // Only need to specify one URI. + explicit RedisCluster(const std::string &uri); + + RedisCluster(const RedisCluster &) = delete; + RedisCluster& operator=(const RedisCluster &) = delete; + + RedisCluster(RedisCluster &&) = default; + RedisCluster& operator=(RedisCluster &&) = default; + + Redis redis(const StringView &hash_tag); + + Pipeline pipeline(const StringView &hash_tag); + + Transaction transaction(const StringView &hash_tag, bool piped = false); + + Subscriber subscriber(); + + template + auto command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // KEY commands. + + long long del(const StringView &key); + + template + long long del(Input first, Input last); + + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + OptionalString dump(const StringView &key); + + long long exists(const StringView &key); + + template + long long exists(Input first, Input last); + + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + bool expire(const StringView &key, long long timeout); + + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + bool expireat(const StringView &key, long long timestamp); + + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + bool persist(const StringView &key); + + bool pexpire(const StringView &key, long long timeout); + + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + bool pexpireat(const StringView &key, long long timestamp); + + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + long long pttl(const StringView &key); + + void rename(const StringView &key, const StringView &newkey); + + bool renamenx(const StringView &key, const StringView &newkey); + + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + long long touch(const StringView &key); + + template + long long touch(Input first, Input last); + + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + long long ttl(const StringView &key); + + std::string type(const StringView &key); + + long long unlink(const StringView &key); + + template + long long unlink(Input first, Input last); + + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + // STRING commands. + + long long append(const StringView &key, const StringView &str); + + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + long long decr(const StringView &key); + + long long decrby(const StringView &key, long long decrement); + + OptionalString get(const StringView &key); + + long long getbit(const StringView &key, long long offset); + + std::string getrange(const StringView &key, long long start, long long end); + + OptionalString getset(const StringView &key, const StringView &val); + + long long incr(const StringView &key); + + long long incrby(const StringView &key, long long increment); + + double incrbyfloat(const StringView &key, double increment); + + template + void mget(Input first, Input last, Output output); + + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + template + void mset(Input first, Input last); + + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + template + bool msetnx(Input first, Input last); + + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + void setex(const StringView &key, + long long ttl, + const StringView &val); + + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + bool setnx(const StringView &key, const StringView &val); + + long long setrange(const StringView &key, long long offset, const StringView &val); + + long long strlen(const StringView &key); + + // LIST commands. + + OptionalStringPair blpop(const StringView &key, long long timeout); + + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + OptionalStringPair brpop(const StringView &key, long long timeout); + + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + OptionalString lindex(const StringView &key, long long index); + + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + long long llen(const StringView &key); + + OptionalString lpop(const StringView &key); + + long long lpush(const StringView &key, const StringView &val); + + template + long long lpush(const StringView &key, Input first, Input last); + + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + long long lpushx(const StringView &key, const StringView &val); + + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + long long lrem(const StringView &key, long long count, const StringView &val); + + void lset(const StringView &key, long long index, const StringView &val); + + void ltrim(const StringView &key, long long start, long long stop); + + OptionalString rpop(const StringView &key); + + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + long long rpush(const StringView &key, const StringView &val); + + template + long long rpush(const StringView &key, Input first, Input last); + + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + long long hdel(const StringView &key, const StringView &field); + + template + long long hdel(const StringView &key, Input first, Input last); + + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + bool hexists(const StringView &key, const StringView &field); + + OptionalString hget(const StringView &key, const StringView &field); + + template + void hgetall(const StringView &key, Output output); + + long long hincrby(const StringView &key, const StringView &field, long long increment); + + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + template + void hkeys(const StringView &key, Output output); + + long long hlen(const StringView &key); + + template + void hmget(const StringView &key, Input first, Input last, Output output); + + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + template + void hmset(const StringView &key, Input first, Input last); + + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + bool hset(const StringView &key, const StringView &field, const StringView &val); + + bool hset(const StringView &key, const std::pair &item); + + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + bool hsetnx(const StringView &key, const std::pair &item); + + long long hstrlen(const StringView &key, const StringView &field); + + template + void hvals(const StringView &key, Output output); + + // SET commands. + + long long sadd(const StringView &key, const StringView &member); + + template + long long sadd(const StringView &key, Input first, Input last); + + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + long long scard(const StringView &key); + + template + void sdiff(Input first, Input last, Output output); + + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + long long sdiffstore(const StringView &destination, const StringView &key); + + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + void sinter(Input first, Input last, Output output); + + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + long long sinterstore(const StringView &destination, const StringView &key); + + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + bool sismember(const StringView &key, const StringView &member); + + template + void smembers(const StringView &key, Output output); + + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + OptionalString spop(const StringView &key); + + template + void spop(const StringView &key, long long count, Output output); + + OptionalString srandmember(const StringView &key); + + template + void srandmember(const StringView &key, long long count, Output output); + + long long srem(const StringView &key, const StringView &member); + + template + long long srem(const StringView &key, Input first, Input last); + + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + template + void sunion(Input first, Input last, Output output); + + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + long long sunionstore(const StringView &destination, const StringView &key); + + template + long long sunionstore(const StringView &destination, Input first, Input last); + + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + long long zcard(const StringView &key); + + template + long long zcount(const StringView &key, const Interval &interval); + + double zincrby(const StringView &key, double increment, const StringView &member); + + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + long long zlexcount(const StringView &key, const Interval &interval); + + Optional> zpopmax(const StringView &key); + + template + void zpopmax(const StringView &key, long long count, Output output); + + Optional> zpopmin(const StringView &key); + + template + void zpopmin(const StringView &key, long long count, Output output); + + // If *output* is an iterator of a container of string, + // we send *ZRANGE key start stop* command. + // If it's an iterator of a container of pair, + // we send *ZRANGE key start stop WITHSCORES* command. + // + // The following code sends *ZRANGE* without the *WITHSCORES* option: + // + // vector result; + // redis.zrange("key", 0, -1, back_inserter(result)); + // + // On the other hand, the following code sends command with *WITHSCORES* option: + // + // unordered_map with_score; + // redis.zrange("key", 0, -1, inserter(with_score, with_score.end())); + // + // This also applies to other commands with the *WITHSCORES* option, + // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrank(const StringView &key, const StringView &member); + + long long zrem(const StringView &key, const StringView &member); + + template + long long zrem(const StringView &key, Input first, Input last); + + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + OptionalDouble zscore(const StringView &key, const StringView &member); + + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + bool pfadd(const StringView &key, const StringView &element); + + template + bool pfadd(const StringView &key, Input first, Input last); + + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first, last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + class Command { + public: + explicit Command(const StringView &cmd_name) : _cmd_name(cmd_name) {} + + template + void operator()(Connection &connection, Args &&...args) const { + CmdArgs cmd_args; + cmd_args.append(_cmd_name, std::forward(args)...); + connection.send(cmd_args); + } + + private: + StringView _cmd_name; + }; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + ReplyUPtr _command(Cmd cmd, Connection &connection, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::true_type, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::false_type, Input &&first, Args &&...args); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _range_command(Cmd cmd, std::true_type, Input input, Args &&...args); + + template + ReplyUPtr _range_command(Cmd cmd, std::false_type, Input input, Args &&...args); + + void _asking(Connection &connection); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + ShardsPool _pool; +}; + +} + +} + +#include "redis_cluster.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.hpp b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.hpp new file mode 100644 index 000000000..61da3f062 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.hpp @@ -0,0 +1,1415 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP + +#include +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" +#include "shards_pool.h" + +namespace sw { + +namespace redis { + +template +auto RedisCluster::command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + return _command(cmd, + std::is_convertible::type, StringView>(), + std::forward(key), + std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type { + auto cmd = Command(cmd_name); + + return _generic_command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type { + auto r = command(cmd_name, std::forward(key), std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(key), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (first == last || std::next(first) == last) { + throw Error("command: invalid range"); + } + + const auto &key = *first; + ++first; + + auto cmd = [&key](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + cmd_args.append(key); + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long RedisCluster::del(Input first, Input last) { + if (first == last) { + throw Error("DEL: no key specified"); + } + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::exists(Input first, Input last) { + if (first == last) { + throw Error("EXISTS: no key specified"); + } + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool RedisCluster::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool RedisCluster::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +inline bool RedisCluster::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool RedisCluster::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void RedisCluster::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long RedisCluster::touch(Input first, Input last) { + if (first == last) { + throw Error("TOUCH: no key specified"); + } + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::unlink(Input first, Input last) { + if (first == last) { + throw Error("UNLINK: no key specified"); + } + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +// STRING commands. + +template +long long RedisCluster::bitop(BitOp op, const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("BITOP: no key specified"); + } + + auto reply = _command(cmd::bitop_range, destination, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::mget(Input first, Input last, Output output) { + if (first == last) { + throw Error("MGET: no key specified"); + } + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::mset(Input first, Input last) { + if (first == last) { + throw Error("MSET: no key specified"); + } + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool RedisCluster::msetnx(Input first, Input last) { + if (first == last) { + throw Error("MSETNX: no key specified"); + } + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void RedisCluster::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void RedisCluster::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair RedisCluster::blpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BLPOP: no key specified"); + } + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BRPOP: no key specified"); + } + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString RedisCluster::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long RedisCluster::lpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("LPUSH: no key specified"); + } + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long RedisCluster::rpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("RPUSH: no key specified"); + } + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long RedisCluster::hdel(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HDEL: no key specified"); + } + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmget(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("HMGET: no key specified"); + } + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmset(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HMSET: no key specified"); + } + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +inline void RedisCluster::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long RedisCluster::sadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SADD: no key specified"); + } + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sdiff(Input first, Input last, Output output) { + if (first == last) { + throw Error("SDIFF: no key specified"); + } + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sdiffstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SDIFFSTORE: no key specified"); + } + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sinter(Input first, Input last, Output output) { + if (first == last) { + throw Error("SINTER: no key specified"); + } + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sinterstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SINTERSTORE: no key specified"); + } + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::srem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SREM: no key specified"); + } + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void RedisCluster::sunion(Input first, Input last, Output output) { + if (first == last) { + throw Error("SUNION: no key specified"); + } + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sunionstore(const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("SUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto RedisCluster::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto RedisCluster::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long RedisCluster::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + if (first == last) { + throw Error("ZADD: no key specified"); + } + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZINTERSTORE: no key specified"); + } + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zrem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("ZREM: no key specified"); + } + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long RedisCluster::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool RedisCluster::pfadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("PFADD: no key specified"); + } + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::pfcount(Input first, Input last) { + if (first == last) { + throw Error("PFCOUNT: no key specified"); + } + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::pfmerge(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("PFMERGE: no key specified"); + } + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long RedisCluster::geoadd(const StringView &key, + Input first, + Input last) { + if (first == last) { + throw Error("GEOADD: no key specified"); + } + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::geohash(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOHASH: no key specified"); + } + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::geopos(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOPOS: no key specified"); + } + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, *keys.begin(), script, keys, args); + + return reply::parse(*reply); +} + +template +void RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, *keys.begin(), script, keys, args); + + reply::to_array(*reply, output); +} + +template +Result RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::evalsha, *keys.begin(), script, keys, args); + + return reply::parse(*reply); +} + +template +void RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = command(cmd::evalsha, *keys.begin(), script, keys, args); + + reply::to_array(*reply, output); +} + +// Stream commands. + +template +long long RedisCluster::xack(const StringView &key, + const StringView &group, + Input first, + Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto RedisCluster::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup, key, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = _command(cmd::xreadgroup_range, + first->first, + group, + consumer, + first, + last, + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup_block, + key, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = _command(cmd::xreadgroup_block_range, + first->first, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type { + return command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type { + auto k = std::to_string(std::forward(key)); + return command(cmd, k, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::true_type, const StringView &key, Args &&...args) { + return _command(cmd, key, key, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::false_type, Input &&first, Args &&...args) { + return _range_command(cmd, + std::is_convertible< + typename std::decay< + decltype(*std::declval())>::type, StringView>(), + std::forward(first), + std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::true_type, Input input, Args &&...args) { + return _command(cmd, *input, input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::false_type, Input input, Args &&...args) { + return _command(cmd, std::get<0>(*input), input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, Connection &connection, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + return connection.recv(); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, const StringView &key, Args &&...args) { + for (auto idx = 0; idx < 2; ++idx) { + try { + auto guarded_connection = _pool.fetch(key); + + return _command(cmd, guarded_connection.connection(), std::forward(args)...); + } catch (const IoError &err) { + // When master is down, one of its replicas will be promoted to be the new master. + // If we try to send command to the old master, we'll get an *IoError*. + // In this case, we need to update the slots mapping. + _pool.update(); + } catch (const ClosedError &err) { + // Node might be removed. + // 1. Get up-to-date slot mapping to check if the node still exists. + _pool.update(); + + // TODO: + // 2. If it's NOT exist, update slot mapping, and retry. + // 3. If it's still exist, that means the node is down, NOT removed, throw exception. + } catch (const MovedError &err) { + // Slot mapping has been changed, update it and try again. + _pool.update(); + } catch (const AskError &err) { + auto guarded_connection = _pool.fetch(err.node()); + auto &connection = guarded_connection.connection(); + + // 1. send ASKING command. + _asking(connection); + + // 2. resend last command. + try { + return _command(cmd, connection, std::forward(args)...); + } catch (const MovedError &err) { + throw Error("Slot migrating... ASKING node hasn't been set to IMPORTING state"); + } + } // For other exceptions, just throw it. + } + + // Possible failures: + // 1. Source node has already run 'CLUSTER SETSLOT xxx NODE xxx', + // while the destination node has NOT run it. + // In this case, client will be redirected by both nodes with MovedError. + // 2. Other failures... + throw Error("Failed to send command with key: " + std::string(key.data(), key.size())); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr RedisCluster::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/reply.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/reply.h new file mode 100644 index 000000000..b309de5bb --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/reply.h @@ -0,0 +1,363 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REPLY_H +#define SEWENEW_REDISPLUSPLUS_REPLY_H + +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +struct ReplyDeleter { + void operator()(redisReply *reply) const { + if (reply != nullptr) { + freeReplyObject(reply); + } + } +}; + +using ReplyUPtr = std::unique_ptr; + +namespace reply { + +template +struct ParseTag {}; + +template +inline T parse(redisReply &reply) { + return parse(ParseTag(), reply); +} + +void parse(ParseTag, redisReply &reply); + +std::string parse(ParseTag, redisReply &reply); + +long long parse(ParseTag, redisReply &reply); + +double parse(ParseTag, redisReply &reply); + +bool parse(ParseTag, redisReply &reply); + +template +Optional parse(ParseTag>, redisReply &reply); + +template +std::pair parse(ParseTag>, redisReply &reply); + +template +std::tuple parse(ParseTag>, redisReply &reply); + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template +long long parse_scan_reply(redisReply &reply, Output output); + +inline bool is_error(redisReply &reply) { + return reply.type == REDIS_REPLY_ERROR; +} + +inline bool is_nil(redisReply &reply) { + return reply.type == REDIS_REPLY_NIL; +} + +inline bool is_string(redisReply &reply) { + return reply.type == REDIS_REPLY_STRING; +} + +inline bool is_status(redisReply &reply) { + return reply.type == REDIS_REPLY_STATUS; +} + +inline bool is_integer(redisReply &reply) { + return reply.type == REDIS_REPLY_INTEGER; +} + +inline bool is_array(redisReply &reply) { + return reply.type == REDIS_REPLY_ARRAY; +} + +std::string to_status(redisReply &reply); + +template +void to_array(redisReply &reply, Output output); + +// Rewrite set reply to bool type +void rewrite_set_reply(redisReply &reply); + +// Rewrite georadius reply to OptionalLongLong type +void rewrite_georadius_reply(redisReply &reply); + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple; + +} + +// Inline implementations. + +namespace reply { + +namespace detail { + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.element == nullptr) { + // Empty array. + return; + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + auto *sub_reply = reply.element[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null array element reply"); + } + + *output = parse::type>(*sub_reply); + + ++output; + } +} + +bool is_flat_array(redisReply &reply); + +template +void to_flat_array(redisReply &reply, Output output) { + if (reply.element == nullptr) { + // Empty array. + return; + } + + if (reply.elements % 2 != 0) { + throw ProtoError("Not string pair array reply"); + } + + for (std::size_t idx = 0; idx != reply.elements; idx += 2) { + auto *key_reply = reply.element[idx]; + auto *val_reply = reply.element[idx + 1]; + if (key_reply == nullptr || val_reply == nullptr) { + throw ProtoError("Null string array reply"); + } + + using Pair = typename IterType::type; + using FirstType = typename std::decay::type; + using SecondType = typename std::decay::type; + *output = std::make_pair(parse(*key_reply), + parse(*val_reply)); + + ++output; + } +} + +template +void to_array(std::true_type, redisReply &reply, Output output) { + if (is_flat_array(reply)) { + to_flat_array(reply, output); + } else { + to_array(reply, output); + } +} + +template +void to_array(std::false_type, redisReply &reply, Output output) { + to_array(reply, output); +} + +template +std::tuple parse_tuple(redisReply **reply, std::size_t idx) { + assert(reply != nullptr); + + auto *sub_reply = reply[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null reply"); + } + + return std::make_tuple(parse(*sub_reply)); +} + +template +auto parse_tuple(redisReply **reply, std::size_t idx) -> + typename std::enable_if>::type { + assert(reply != nullptr); + + return std::tuple_cat(parse_tuple(reply, idx), + parse_tuple(reply, idx + 1)); +} + +} + +template +Optional parse(ParseTag>, redisReply &reply) { + if (reply::is_nil(reply)) { + return {}; + } + + return Optional(parse(reply)); +} + +template +std::pair parse(ParseTag>, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != 2) { + throw ProtoError("NOT key-value PAIR reply"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null PAIR reply"); + } + + auto *first = reply.element[0]; + auto *second = reply.element[1]; + if (first == nullptr || second == nullptr) { + throw ProtoError("Null pair reply"); + } + + return std::make_pair(parse::type>(*first), + parse::type>(*second)); +} + +template +std::tuple parse(ParseTag>, redisReply &reply) { + constexpr auto size = sizeof...(Args); + + static_assert(size > 0, "DO NOT support parsing tuple with 0 element"); + + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != size) { + throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null TUPLE reply"); + } + + return detail::parse_tuple(reply.element, 0); +} + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::back_inserter(container)); + + return container; +} + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::inserter(container, container.end())); + + return container; +} + +template +long long parse_scan_reply(redisReply &reply, Output output) { + if (reply.elements != 2 || reply.element == nullptr) { + throw ProtoError("Invalid scan reply"); + } + + auto *cursor_reply = reply.element[0]; + auto *data_reply = reply.element[1]; + if (cursor_reply == nullptr || data_reply == nullptr) { + throw ProtoError("Invalid cursor reply or data reply"); + } + + auto cursor_str = reply::parse(*cursor_reply); + auto new_cursor = 0; + try { + new_cursor = std::stoll(cursor_str); + } catch (const std::exception &e) { + throw ProtoError("Invalid cursor reply: " + cursor_str); + } + + reply::to_array(*data_reply, output); + + return new_cursor; +} + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + detail::to_array(typename IsKvPairIter::type(), reply, output); +} + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple { + if (!is_array(reply) || reply.elements != 4) { + throw ProtoError("expect array reply with 4 elements"); + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + if (reply.element[idx] == nullptr) { + throw ProtoError("null array reply"); + } + } + + auto num = parse(*(reply.element[0])); + auto start = parse(*(reply.element[1])); + auto end = parse(*(reply.element[2])); + + auto &entry_reply = *(reply.element[3]); + if (!is_nil(entry_reply)) { + to_array(entry_reply, output); + } + + return std::make_tuple(num, std::move(start), std::move(end)); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REPLY_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/sentinel.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/sentinel.h new file mode 100644 index 000000000..e80d1e56a --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/sentinel.h @@ -0,0 +1,138 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SENTINEL_H +#define SEWENEW_REDISPLUSPLUS_SENTINEL_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "shards.h" +#include "reply.h" + +namespace sw { + +namespace redis { + +struct SentinelOptions { + std::vector> nodes; + + std::string password; + + bool keep_alive = true; + + std::chrono::milliseconds connect_timeout{100}; + + std::chrono::milliseconds socket_timeout{100}; + + std::chrono::milliseconds retry_interval{100}; + + std::size_t max_retry = 2; +}; + +class Sentinel { +public: + explicit Sentinel(const SentinelOptions &sentinel_opts); + + Sentinel(const Sentinel &) = delete; + Sentinel& operator=(const Sentinel &) = delete; + + Sentinel(Sentinel &&) = delete; + Sentinel& operator=(Sentinel &&) = delete; + + ~Sentinel() = default; + +private: + Connection master(const std::string &master_name, const ConnectionOptions &opts); + + Connection slave(const std::string &master_name, const ConnectionOptions &opts); + + class Iterator; + + friend class SimpleSentinel; + + std::list _parse_options(const SentinelOptions &opts) const; + + Optional _get_master_addr_by_name(Connection &connection, const StringView &name); + + std::vector _get_slave_addr_by_name(Connection &connection, const StringView &name); + + Connection _connect_redis(const Node &node, ConnectionOptions opts); + + Role _get_role(Connection &connection); + + std::vector _parse_slave_info(redisReply &reply) const; + + std::list _healthy_sentinels; + + std::list _broken_sentinels; + + SentinelOptions _sentinel_opts; + + std::mutex _mutex; +}; + +class SimpleSentinel { +public: + SimpleSentinel(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role); + + SimpleSentinel() = default; + + SimpleSentinel(const SimpleSentinel &) = default; + SimpleSentinel& operator=(const SimpleSentinel &) = default; + + SimpleSentinel(SimpleSentinel &&) = default; + SimpleSentinel& operator=(SimpleSentinel &&) = default; + + ~SimpleSentinel() = default; + + explicit operator bool() const { + return bool(_sentinel); + } + + Connection create(const ConnectionOptions &opts); + +private: + std::shared_ptr _sentinel; + + std::string _master_name; + + Role _role = Role::MASTER; +}; + +class StopIterError : public Error { +public: + StopIterError() : Error("StopIterError") {} + + StopIterError(const StopIterError &) = default; + StopIterError& operator=(const StopIterError &) = default; + + StopIterError(StopIterError &&) = default; + StopIterError& operator=(StopIterError &&) = default; + + virtual ~StopIterError() = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SENTINEL_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards.h new file mode 100644 index 000000000..a0593acbc --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards.h @@ -0,0 +1,115 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_H + +#include +#include +#include "errors.h" + +namespace sw { + +namespace redis { + +using Slot = std::size_t; + +struct SlotRange { + Slot min; + Slot max; +}; + +inline bool operator<(const SlotRange &lhs, const SlotRange &rhs) { + return lhs.max < rhs.max; +} + +struct Node { + std::string host; + int port; +}; + +inline bool operator==(const Node &lhs, const Node &rhs) { + return lhs.host == rhs.host && lhs.port == rhs.port; +} + +struct NodeHash { + std::size_t operator()(const Node &node) const noexcept { + auto host_hash = std::hash{}(node.host); + auto port_hash = std::hash{}(node.port); + return host_hash ^ (port_hash << 1); + } +}; + +using Shards = std::map; + +class RedirectionError : public ReplyError { +public: + RedirectionError(const std::string &msg); + + RedirectionError(const RedirectionError &) = default; + RedirectionError& operator=(const RedirectionError &) = default; + + RedirectionError(RedirectionError &&) = default; + RedirectionError& operator=(RedirectionError &&) = default; + + virtual ~RedirectionError() = default; + + Slot slot() const { + return _slot; + } + + const Node& node() const { + return _node; + } + +private: + std::pair _parse_error(const std::string &msg) const; + + Slot _slot = 0; + Node _node; +}; + +class MovedError : public RedirectionError { +public: + explicit MovedError(const std::string &msg) : RedirectionError(msg) {} + + MovedError(const MovedError &) = default; + MovedError& operator=(const MovedError &) = default; + + MovedError(MovedError &&) = default; + MovedError& operator=(MovedError &&) = default; + + virtual ~MovedError() = default; +}; + +class AskError : public RedirectionError { +public: + explicit AskError(const std::string &msg) : RedirectionError(msg) {} + + AskError(const AskError &) = default; + AskError& operator=(const AskError &) = default; + + AskError(AskError &&) = default; + AskError& operator=(AskError &&) = default; + + virtual ~AskError() = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards_pool.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards_pool.h new file mode 100644 index 000000000..1184806e9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards_pool.h @@ -0,0 +1,137 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H + +#include +#include +#include +#include +#include +#include "reply.h" +#include "connection_pool.h" +#include "shards.h" + +namespace sw { + +namespace redis { + +using ConnectionPoolSPtr = std::shared_ptr; + +class GuardedConnection { +public: + GuardedConnection(const ConnectionPoolSPtr &pool) : _pool(pool), + _connection(_pool->fetch()) { + assert(!_connection.broken()); + } + + GuardedConnection(const GuardedConnection &) = delete; + GuardedConnection& operator=(const GuardedConnection &) = delete; + + GuardedConnection(GuardedConnection &&) = default; + GuardedConnection& operator=(GuardedConnection &&) = default; + + ~GuardedConnection() { + _pool->release(std::move(_connection)); + } + + Connection& connection() { + return _connection; + } + +private: + ConnectionPoolSPtr _pool; + Connection _connection; +}; + +class ShardsPool { +public: + ShardsPool() = default; + + ShardsPool(const ShardsPool &that) = delete; + ShardsPool& operator=(const ShardsPool &that) = delete; + + ShardsPool(ShardsPool &&that); + ShardsPool& operator=(ShardsPool &&that); + + ~ShardsPool() = default; + + ShardsPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + // Fetch a connection by key. + GuardedConnection fetch(const StringView &key); + + // Randomly pick a connection. + GuardedConnection fetch(); + + // Fetch a connection by node. + GuardedConnection fetch(const Node &node); + + void update(); + + ConnectionOptions connection_options(const StringView &key); + + ConnectionOptions connection_options(); + +private: + void _move(ShardsPool &&that); + + void _init_pool(const Shards &shards); + + Shards _cluster_slots(Connection &connection) const; + + ReplyUPtr _cluster_slots_command(Connection &connection) const; + + Shards _parse_reply(redisReply &reply) const; + + std::pair _parse_slot_info(redisReply &reply) const; + + // Get slot by key. + std::size_t _slot(const StringView &key) const; + + // Randomly pick a slot. + std::size_t _slot() const; + + ConnectionPoolSPtr& _get_pool(Slot slot); + + GuardedConnection _fetch(Slot slot); + + ConnectionOptions _connection_options(Slot slot); + + using NodeMap = std::unordered_map; + + NodeMap::iterator _add_node(const Node &node); + + ConnectionPoolOptions _pool_opts; + + ConnectionOptions _connection_opts; + + Shards _shards; + + NodeMap _pools; + + std::mutex _mutex; + + static const std::size_t SHARDS = 16383; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/subscriber.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/subscriber.h new file mode 100644 index 000000000..8b7c5cfb4 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/subscriber.h @@ -0,0 +1,231 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H +#define SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H + +#include +#include +#include +#include "connection.h" +#include "reply.h" +#include "command.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +// @NOTE: Subscriber is NOT thread-safe. +// Subscriber uses callbacks to handle messages. There are 6 kinds of messages: +// 1) MESSAGE: message sent to a channel. +// 2) PMESSAGE: message sent to channels of a given pattern. +// 3) SUBSCRIBE: meta message sent when we successfully subscribe to a channel. +// 4) UNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel. +// 5) PSUBSCRIBE: meta message sent when we successfully subscribe to a channel pattern. +// 6) PUNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel pattern. +// +// Use Subscriber::on_message(MsgCallback) to set the callback function for message of +// *MESSAGE* type, and the callback interface is: +// void (std::string channel, std::string msg) +// +// Use Subscriber::on_pmessage(PatternMsgCallback) to set the callback function for message of +// *PMESSAGE* type, and the callback interface is: +// void (std::string pattern, std::string channel, std::string msg) +// +// Messages of other types are called *META MESSAGE*, they have the same callback interface. +// Use Subscriber::on_meta(MetaCallback) to set the callback function: +// void (Subscriber::MsgType type, OptionalString channel, long long num) +// +// NOTE: If we haven't subscribe/psubscribe to any channel/pattern, and try to +// unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all +// channels/patterns, *channel* will be null. So the second parameter of meta callback +// is of type *OptionalString*. +// +// All these callback interfaces pass std::string by value, and you can take their ownership +// (i.e. std::move) safely. +// +// If you don't set callback for a specific kind of message, Subscriber::consume() will +// receive the message, and ignore it, i.e. no callback will be called. +class Subscriber { +public: + Subscriber(const Subscriber &) = delete; + Subscriber& operator=(const Subscriber &) = delete; + + Subscriber(Subscriber &&) = default; + Subscriber& operator=(Subscriber &&) = default; + + ~Subscriber() = default; + + enum class MsgType { + SUBSCRIBE, + UNSUBSCRIBE, + PSUBSCRIBE, + PUNSUBSCRIBE, + MESSAGE, + PMESSAGE + }; + + template + void on_message(MsgCb msg_callback); + + template + void on_pmessage(PMsgCb pmsg_callback); + + template + void on_meta(MetaCb meta_callback); + + void subscribe(const StringView &channel); + + template + void subscribe(Input first, Input last); + + template + void subscribe(std::initializer_list channels) { + subscribe(channels.begin(), channels.end()); + } + + void unsubscribe(); + + void unsubscribe(const StringView &channel); + + template + void unsubscribe(Input first, Input last); + + template + void unsubscribe(std::initializer_list channels) { + unsubscribe(channels.begin(), channels.end()); + } + + void psubscribe(const StringView &pattern); + + template + void psubscribe(Input first, Input last); + + template + void psubscribe(std::initializer_list channels) { + psubscribe(channels.begin(), channels.end()); + } + + void punsubscribe(); + + void punsubscribe(const StringView &channel); + + template + void punsubscribe(Input first, Input last); + + template + void punsubscribe(std::initializer_list channels) { + punsubscribe(channels.begin(), channels.end()); + } + + void consume(); + +private: + friend class Redis; + + friend class RedisCluster; + + explicit Subscriber(Connection connection); + + MsgType _msg_type(redisReply *reply) const; + + void _check_connection(); + + void _handle_message(redisReply &reply); + + void _handle_pmessage(redisReply &reply); + + void _handle_meta(MsgType type, redisReply &reply); + + using MsgCallback = std::function; + + using PatternMsgCallback = std::function; + + using MetaCallback = std::function; + + using TypeIndex = std::unordered_map; + static const TypeIndex _msg_type_index; + + Connection _connection; + + MsgCallback _msg_callback = nullptr; + + PatternMsgCallback _pmsg_callback = nullptr; + + MetaCallback _meta_callback = nullptr; +}; + +template +void Subscriber::on_message(MsgCb msg_callback) { + _msg_callback = msg_callback; +} + +template +void Subscriber::on_pmessage(PMsgCb pmsg_callback) { + _pmsg_callback = pmsg_callback; +} + +template +void Subscriber::on_meta(MetaCb meta_callback) { + _meta_callback = meta_callback; +} + +template +void Subscriber::subscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::subscribe_range(_connection, first, last); +} + +template +void Subscriber::unsubscribe(Input first, Input last) { + _check_connection(); + + cmd::unsubscribe_range(_connection, first, last); +} + +template +void Subscriber::psubscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::psubscribe_range(_connection, first, last); +} + +template +void Subscriber::punsubscribe(Input first, Input last) { + _check_connection(); + + cmd::punsubscribe_range(_connection, first, last); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/transaction.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/transaction.h new file mode 100644 index 000000000..f19f24889 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/transaction.h @@ -0,0 +1,77 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TRANSACTION_H +#define SEWENEW_REDISPLUSPLUS_TRANSACTION_H + +#include +#include +#include "connection.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +class TransactionImpl { +public: + explicit TransactionImpl(bool piped) : _piped(piped) {} + + template + void command(Connection &connection, Cmd cmd, Args &&...args); + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t cmd_num); + +private: + void _open_transaction(Connection &connection); + + void _close_transaction(); + + void _get_queued_reply(Connection &connection); + + void _get_queued_replies(Connection &connection, std::size_t cmd_num); + + std::vector _exec(Connection &connection); + + void _discard(Connection &connection); + + bool _in_transaction = false; + + bool _piped; +}; + +template +void TransactionImpl::command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + if (!_in_transaction) { + _open_transaction(connection); + } + + cmd(connection, std::forward(args)...); + + if (!_piped) { + _get_queued_reply(connection); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TRANSACTION_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/utils.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/utils.h new file mode 100644 index 000000000..e29e64e14 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/utils.h @@ -0,0 +1,269 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_UTILS_H +#define SEWENEW_REDISPLUSPLUS_UTILS_H + +#include +#include +#include + +namespace sw { + +namespace redis { + +// By now, not all compilers support std::string_view, +// so we make our own implementation. +class StringView { +public: + constexpr StringView() noexcept = default; + + constexpr StringView(const char *data, std::size_t size) : _data(data), _size(size) {} + + StringView(const char *data) : _data(data), _size(std::strlen(data)) {} + + StringView(const std::string &str) : _data(str.data()), _size(str.size()) {} + + constexpr StringView(const StringView &) noexcept = default; + + StringView& operator=(const StringView &) noexcept = default; + + constexpr const char* data() const noexcept { + return _data; + } + + constexpr std::size_t size() const noexcept { + return _size; + } + +private: + const char *_data = nullptr; + std::size_t _size = 0; +}; + +template +class Optional { +public: + Optional() = default; + + Optional(const Optional &) = default; + Optional& operator=(const Optional &) = default; + + Optional(Optional &&) = default; + Optional& operator=(Optional &&) = default; + + ~Optional() = default; + + template + explicit Optional(Args &&...args) : _value(true, T(std::forward(args)...)) {} + + explicit operator bool() const { + return _value.first; + } + + T& value() { + return _value.second; + } + + const T& value() const { + return _value.second; + } + + T* operator->() { + return &(_value.second); + } + + const T* operator->() const { + return &(_value.second); + } + + T& operator*() { + return _value.second; + } + + const T& operator*() const { + return _value.second; + } + +private: + std::pair _value; +}; + +using OptionalString = Optional; + +using OptionalLongLong = Optional; + +using OptionalDouble = Optional; + +using OptionalStringPair = Optional>; + +template +struct IsKvPair : std::false_type {}; + +template +struct IsKvPair> : std::true_type {}; + +template +using Void = void; + +template > +struct IsInserter : std::false_type {}; + +template +//struct IsInserter> : std::true_type {}; +struct IsInserter::value>::type> + : std::true_type {}; + +template > +struct IterType { + using type = typename std::iterator_traits::value_type; +}; + +template +//struct IterType> { +struct IterType::value>::type> { + typename std::enable_if::value>::type> { + using type = typename std::decay::type; +}; + +template > +struct IsIter : std::false_type {}; + +template +struct IsIter::value>::type> : std::true_type {}; + +template +//struct IsIter::iterator_category>> +struct IsIter::value_type>::value>::type> + : std::integral_constant::value> {}; + +template +struct IsKvPairIter : IsKvPair::type> {}; + +template +struct TupleWithType : std::false_type {}; + +template +struct TupleWithType> : std::false_type {}; + +template +struct TupleWithType> : TupleWithType> {}; + +template +struct TupleWithType> : std::true_type {}; + +template +struct IndexSequence {}; + +template +struct MakeIndexSequence : MakeIndexSequence {}; + +template +struct MakeIndexSequence<0, Is...> : IndexSequence {}; + +// NthType and NthValue are taken from +// https://stackoverflow.com/questions/14261183 +template +struct NthType {}; + +template +struct NthType<0, Arg, Args...> { + using type = Arg; +}; + +template +struct NthType { + using type = typename NthType::type; +}; + +template +struct LastType { + using type = typename NthType::type; +}; + +struct InvalidLastType {}; + +template <> +struct LastType<> { + using type = InvalidLastType; +}; + +template +auto NthValue(Arg &&arg, Args &&...) + -> typename std::enable_if<(I == 0), decltype(std::forward(arg))>::type { + return std::forward(arg); +} + +template +auto NthValue(Arg &&, Args &&...args) + -> typename std::enable_if<(I > 0), + decltype(std::forward::type>( + std::declval::type>()))>::type { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template +auto LastValue(Args &&...args) + -> decltype(std::forward::type>( + std::declval::type>())) { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template > +struct HasPushBack : std::false_type {}; + +template +struct HasPushBack().push_back(std::declval()) + )>::value>::type> : std::true_type {}; + +template > +struct HasInsert : std::false_type {}; + +template +struct HasInsert().insert(std::declval(), + std::declval())), + typename T::iterator>::value>::type> : std::true_type {}; + +template +struct IsSequenceContainer + : std::integral_constant::value + && !std::is_same::type, std::string>::value> {}; + +template +struct IsAssociativeContainer + : std::integral_constant::value && !HasPushBack::value> {}; + +uint16_t crc16(const char *buf, int len); + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_UTILS_H From 5d47697adedfaa3380b50c18d80d660dfcd5ecfe Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 12 May 2020 13:04:16 -0700 Subject: [PATCH 057/362] helps to add the actual library --- .../install/centos8/lib/libredis++.a | Bin 0 -> 1168436 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/lib/libredis++.a diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/lib/libredis++.a b/ext/redis-plus-plus-1.1.1/install/centos8/lib/libredis++.a new file mode 100644 index 0000000000000000000000000000000000000000..c605b0ef0c1b0b43ccedb4b6969d3adb53897fe5 GIT binary patch literal 1168436 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

u({r6lS_Qx9HkM&_6YluJ2 zhkcyEez%YQ)5l;x#7F#q!9LbU`wbU+DV+O1fAOLJQbYWM4Dn-VHsD)bWVztX4g69l z_Qhw{L(Y!6A5@{o&<>>~B3S`a2D*?~+NPzrzzq8HN!nHQiFVcp*{AwxAyU5>(t+sTkp3{;>&4hJ+9Y(sKq`a;F{=qb^FB@`zWyw7k?qy zJNei2*A%*d$}{KxS>o#X=ldj0^^frwzatr&;%_#@f6fs9K1=*KiJwe1xas+uZHXUo zm0N#ti#9t@zv&EkK-^+&hw_D;zrMZUT;@@hCpHxI|(3b7IMmX*N3hnFl zf6fv=L*n!J#!YYkr!Dbk8{#h^M|%8IXyW4jcYrImKGXvepZgYW%*BmAlQi|;DzX1F zl4)Pp{~+0$`tJ^d{^ew^>tAh&A2-+y+&@fwwWjBOj9X^03rII~+HWNh^!UG6;+IMM zaP6n=sng?EO8jgQ#r^l+hWLC>iK+eGmiXb?Z>ZS+NvnUY*uShbxZZwhO`_|MC~+N@ zg8tVH`fs%8zdzkI45xpE(l2pT@sQYeJ5(WVsZ}%LRkG(8$-%UUQqf;|ocjjnzlL`8 z_J3F6>wecOad8zrs-e_YCoOTjCcM zx$(o*|7T15Iz#>6H^fh%g)is7yBn_l^=}gY$a=)xQQbk6v&3HSKh#n=Sh5N?g6CYWkJE(m#sMGkIUvA2ZQ4oU7UE`Y$JYlm6&g zuJCNJXZ`DGU&UAba;+tPjl|Cg6Tik1ztj+agCTww5@f9ZBscJqF!4_zdsF?_NqkjL zuK#C-_>W0^z5jhC@ekJGvsT_zd0H#8{yMQ&_hGtLp~wG|$=;;DPV`T6o6$M`enC$3 z_Itn*KlvIr;#?J2O1=NwXNg}rJGB37HpGuQ$gRJgzYP+X)$FKW8H-5PMcO!dK{J6h|*8dwr{2i9~8D(z# zaP|Mr5`V}2q4B>n#9vD5BV+!r^A`VQvNzSgdTwa^?+x+qY-5gJcfA|`k}&Om8`+!U zPkS&l{*Q+Eqi8}f)j#7#Z}BfDdsF<3hePB4WQgC7zI<+qUv-lke`J{Y_aS>z{OCtQ z6x_%XM5tA8Zfo8s5X7hrJw?J~q)Z;4+v&0G8rEb-?(8e0F|hWJk%ZmxfA zxf}n=F!_I+>`nDAd^|LMGxDV$zwW2=15^8FOn2kwg^B-HvNy#~`e$hT0}b)7q63sE ze)SA*`OhbNQ~Zdk(D*G3@dqAZjvsTIxBB-ZdsF`nEL z;R{=|;rKtq5P#lL=J-{2xOc3X0Ny3&17%VU%JTEzfSCV z{n*Z+|0Rq5yn9?jIy?t$O1CO^S@f5QeK`F)Ec!DRhw4Alpug*}=KOb_?aHRZOVFlt ztMV4H*Ylq&_Adolho1jjPxhw#cX}yQ|Ir5hZ(H>LD)H&?6tpSbsyvCVZ}GmaKjvQ7 zY*~7VvWsQ!};`iDz=z5mRb}#d z-&*wFa-SQRmUls$>Nlz!9AnOZrPznlpGx+o{3k65)!)US|1OEI=l|-zyRyUepKmPs zE5!Z-*G>Jae*D@>_9p#NOGEW{Gw4t5U>?6$-0y}AH-FwE_WJlO6Z>%a{|nih^lvuk z?{3gvYtf%s>FTHM6||}GPp+J;&xz*#Q&#D|A1?pB$lj#CdRb`x6Ak)rmH2x9xp=M{ zpH6RsHln-{V#XemAKd|VZy*yNZ5(QQHixyQUcQp5(K@YeDy}v5ms=P+*_5PDB z_TltTBzsf-3s;2dKhvPU+M>VLgRcH?u#QH~&9n(H|rB;qw1D*_-k|@3m0Xc`6pZK_5Kqz&#nJQZc+cL z`@4kfP5R4Lhw2|<(Eq4K|5k}lr#C?xxo|dBw&`TTP1$D`E!{~|D$d_!|AWK=#P6NRR1u8{v$e@`_ER1AMX5pyx8mgC*m=0`p1#I zDgTjghU&k-p#NVM{U1qu?e-7nT$h!p{?qzYbN{LHsJ|uIoAlQj^j~DqKV0JL{b%*# zZvMjQf7GJCR_w#|pNB2_7uJO4pRa{Qy0RkF`sYVW{OTuMqFhE5tI+eOZ!PgF-g4tt zizQ!wy4(=I^J(V(d)_}?L#0y*Q0=bDE5%;#zlCBSs|=;o=f5#zZ)*RHw_W|&V#oS( z4Epc4=)X(ihnqiYE&8iG>i^K9zsjJWpUJ7W|KVNC`ClXP6r>@+7`Ke}5N|7MH+ z7*^18 z{&!-$dHiOGeK`Hck-bTO&HJJK=URjQJc+N5-vj^UP5(<4{V`%6PXF^3{qsHu)qlM~ ze+v>$aLE0m?@0VMZZ+y(nai6hGsRx-KXo4U4wfL)qj&g|9uwy4?gGWZ0i>F zuS&NnzqRPE7W;7d-)7OD`BA9;X$JkhdzkZIywH_=NWF4*N9k7Otzxg|zfA1I>A#8W zP5F=eI8^_1gZ|eo`g=U@>J2ylbV#s_U$GCT|5&m&>5u#*RR3)T{bMCQHDl0r;0x}b zhXtVx{a)pNu|jbAPHeExK|A^t}aUvK}b7rBPv^4FuMx&Ky( zeIShfu4He@U&aPkKOG*OjrHGa&_6}u>-yJ8{BZNfTNeFw9`&!b=r1$qpJUJ;-OJp6 zlNP)6ZY}j!1-WoGRpyJm-hVU1K3x9ClD#SaRR;a{8}vVI(LdXx{y!y|`%jfe{msbU zq`%gn{{e&kbcwI`pB)lET>qJC(I54a+fL!~KgXg!>eJBv|FA*-W{dtziLcKWs^6$G zxwkq0g&y^vN%p4vCmZz7Gw8on;_LZeDDlJf|4%IXYdq?I-=e?Jp#L$0{@63j<2U+c zxBU-xi~3iUcU4|3_WJmZ6Z>%er->qSo9}VyZH{cejnP$ zoc|2557&Q&ki9AYaR&X*8T9{M;_LaJE%6U?t5N@|`i&}ovgoh!sDHaff3`vY3kLlI z`*)bsqKKXVG6~(7)QC|4WPhq$Lr~7EXV1KXd*w#6DdAIg{*7`L8nQf776U zn#9-hKU?C58^0e~^jCS*|E@)UtwI0W2K_Pp&Et26#Bc95L;Wkca5h!`pV;f;H)^TN z3zz@NWN*rU)aM5{4siUgHRylCqCZpOhnxQoA7Ji3g<^k?TaEfx{e0q3vN!2Z7X9nQ zlF#4Q8T1d9_f92*-{TmJXo2Q!de~-j(6{i2BioKryN{{-J$=;Oz(k-F- zHyQMoTJ-l?=?V|Get5~EKSS)p<^Oq${){g}^?zy5|AR&UCW+rNO#TxGn&;0tkNV@t z-jx4NUxn)5X3#%I;_LJ0EvsDN;q*Uj(O)6<;qqT;(I2rjRDYd8|GO6br~KPn|2cM$ zIseIGA5Q;~WN*swJkLNKN$3H zw&*W^%?*5z7N1-=n<~!;nDbvL_Tls=lD#Sa)BY2x|0jd~|C9K7{?k^wIwM7|DwJ+j zzG2ayE%xE`ud?XR{yJ3uPJ@1aP9AH~^|yN6RoqGmms06gWscbE^M8!khtq#4*_-m8 zR2Qm$w?Y3y5?|N9O5%sh|8|T1T95j_w&;)gCRG0c1SvhylFy!l&G|2X!zHu~lmDB< zUeAAp*w5GO_3_KkxijT|vq66ggZ||f{k`9G1FQNf8LHo?@`!WH<2OU>!|88J_9p$+ z--hNt(x5+6;_LnACy)B)S@cKMxHS%^|3QoX+24igKiHuEON;*JCH}!G@lvY1tFmvp zIsY{t^(T?NDgTArL-ikO(0{$e*YiL5Emv^3>sKFG^jC;|xcslR=ns4!s=uv4f9w$R z`0f3+t3TZMEf#xy{3eTiIQH->Y)AMSr!}htq#2*_-lT^J8fKk2dK4$f7@Q zjVn1^{=1xK&VQNMhtq!w*_-sw+Yze&ID`HP5?{}M=e6GQUv1H!EcW5_zi824_EV_- z7=!-i8Rqf3MdF9c|8TL_$8W^DZvDgQKacE9`Oo}0RDVZ<{<|&u=S%!>`oFN~ulA^a zgGGNFKWv^hync1EL4T+7&H2xJ&&^*ex2S(r<5rcE#a_>Unb?QRe+k)}@*lY~R6jq9 zQ{_{w-xgW)cb1B`saNjqDBY@TH`F|Slf^!q{x)Q9(qC)P-^HN+a*3~x-z^e9T>hW5 z=#O~ct#3H}k6QFE+!dPtZU+6|TJ+DC_^Q2BGpM|)vi~r1{;NIe?@RWk{8#)Ms=vEI z|BVt~&wt(ruHta|*I4wIiG8^IziH7wZg;5uM1%e#hMUK4=MTNRr;ELw|0=N$m;al|-jx43gZ{G&`fDuuFZ$S9|B1Q4JbnwsKAiqz$lj!X3`ayKThn!>3_nazw*FP{V4|h+b#Ov_ozSRLUaD>JnBE2>`nPE zZ62zBkU{^=5?{~%z3bilAL17EuWH^`<%bsiRbs!a-qPKP(*AzeqCcZWsQ$qQ{U>Ic z$M3{iH&8hJSBt$qek;U2ocECS8Kdn`${)-Iy&$z@qewRu7aQ%O#*z4oB#-sjeWN*rUHvd=`ZR^}LIP0HF4f@}< z=%4kuYZz|+IptDw|EUuDaQQ!x>`nTU4i43yWzauC;_Llq;uo&|qon?-Q2j=gQ!Vzl zi9KB&4BC{PDi6ENT>o0Jk0;Hz>GR1UWN)f}RBN~Xm158LuZ%L(|L>OgF&fhqL{-i8(`$cVa?Vfjw`d2-E53)DK&pa$N{uPG!H%olI|E`kw;rd@pwpoA7 zHdpd7EDknZ|1sHS{S}A1`Xj}j^T)sSrOrpA#pmN>&nf25ws1?9hU;uU`LM7X6Xmx`Fll>H2qA z^hZUz?T7lWHt3HTX>Pw5OMG2_n%L|4PqNtS`cuf>l>bbF{%Z{Si!A!HB|a_hg0`5T zzvwr8Ec&ZG>VMCoztW(8szLubqs-%Xoy6~?>DBW$knBzQ+br?3#h&}`wTAe`miQ6h zxrX85Pq4%f9Ce`M0OMb0h(Fg7zmvpI68);s>p#a5f1bpz5JRs24Tks!k2d%JY>BV; zcRl_9vDf>5q1fwu{qM8L-qe0cN4xn`o1Q^=CbLY_=H3 z51XW|F3%|NS(UEJ)kdGN-72(d*xq-Z9yC8snmWr~2Wh z+jr}k5QAHMVp8uO@jViHbdQONkBf_g*Pcx6-a9ENz9(B`A9QVM`oII4$%R4+(k$XE zXDxy8oc(Vb6ec@Ba>T>~N)BKx&(MD^7tsG_>Hojv@8_soDEqvIdqMVJq;e7cf0_PQ z(|@*IO#fe^|F6*hSLr`vOX&Yn`p@St{P%MDze4s`Qn`x$|6Bfkjmp)s{}z>R%l|a3Tg|eSXX1! zOl67ePonZ_*`G}1HL^d2%Biw{EtO@me?65q$o@@K-Yomms4SQL=~T{;{oAO#UH0#w z@-MP~CzW@}{@qmGBm4JK`B&MWL*;$4e?OI#vi|^;56b?-RQ^Nu=TSLd_8+72aoPVT zl~2n4Q&c`J`wOUiR`#Exa-r?60NrUD;np<@>V#A(bD={wGwfm;DV?ek%JLsr+2_H&MA+_P?a^ zE7{*h<$q+qj>>Oj|2rzT%l;2k{wVuDQTem%@1$~jJ#b z_wKcleZCK_we0gfWo=}i@6S3+_W53?_Oj3S4Mod7--C0E?DPFB$IE^Ul^tZiBbBkT ze=?P)$UcAmf2!rAj%F%0aRpkTQ+R!Lomjl<8FRwS2ZcSIYCK znM3^R=5i*}sCyD`h{QO1>_^+YM(opLlTm+ws*)#8kPDpOxJ47}jIHG~yZ+QC5vC$)j z1>A6vO@kD&TrL~bPJRZpudJi z2g=WhVviKZaXO|=L3*%Fo6hO6aRifPo3_KEzk0o5OhtqK5rOhu!vfcB<#kbD=B2SQ zffdbTIVXV?DY21m4c7C%bJE~yEdL@<{sE~L#ZzJn12YF?2YRn4X;BYl5R@DYbqd_D zBs$Q0y<%Orl!IP!*^)r{GG3-caK8JE=r`QW=5nnIpn5oZEoB^^tu}& zngv!2h-OnNW2EdPrE>;Cc#`~`EM=Ev?z_vL(|~BWGspE*}v|o+thzFg=yt`+9dq z6bJ6HPy(m6=69#nzZ<<}GQH#UuNzV$n{7GT`KydYPfu;__-p;u>OlDtC+(`NaPoga zzw$S*5h}=NA2tH7MY$1{4~U9LnO5BPw>CzW))<%lw#MMv*EdGL%a>FHwa0_?88@}Z zP4(|aZ+Zsnboo9=gb7EapGXBc{6DVIV!{QS)Rsz!(#n!R* znF9)O6fTObanX!mbRMHOEa?QplRP+aZrZK-CUEWaKH{#YYV3%jIf+KHTR~?A;0cP<*3Sfi@=rX^1cCA|C_{nr{Mg9! z@-DG%)~3^60UA!RGD+~z8dpEGc;Ji495s`aSVCL`^G%-7^H&suJ z=VM#>Cz9trD&c)%0}Z|EhNUrbW?QP?B%AaNvP-6eF0a_7p5 zVQJ+xf%3Q9!@bE_33Vpwt{Z>cy_@?A^{o2wbHmbLXFT6Fx<0@4)vFs++c6dYb$#L5 z!*6}V`SaMmPAA8#?D{sgKacgj>)Xc7 zUy4_Mja}dU%wG(jaz+MbPKk|9pGha8a`==ZI-w1F+L0tC=t!d0Px|rX5C3YF<*qM( zLw@v`k5Y>(7xQ|^NB;je{5$gnUuz-d+#hKz#+_68rPD{HOO_ByE3u3JK`XJ~ zVNl0ll~Dh*@>L$Eh+Uv`aD36e{`jz&b?-$4z7Hl#RjeXLOOW)vOxJeL5J7q^3C)afpcx& zy>No-KflL=eWkzl^G4Ah1DBLwIlTEPaUbn=_qN}*X-gleIK$xEHc_-opW6g2lK*Gc z3yr*<@zZ|$e0@{jzw5iV=_+1ONB5=Q>D)V<_36yvvCZ8@s_Evg1_h|ZnE0ULw?nTy&plEbmc{;7h>yHC_T9DIK=Z5nk z&X}{_|Bdy-ZVl-Sx<3A2$fq;kQvJ$HVp~J|EkB>nZQMkrQ(ljC66d$oxIUD;IPMJ5 zZfBrI>%`rXmVMtitp+_Z8v98ni0JO~6>YK&K75mJJkJb0e&fkh&}8YHt--X&?zR5W zhTfLz|1@dRhvWQlLH*@;_xf~w|4>&doB`lwj*>_NVR=wT42>e6TL=4gK0giW5C5C~ zwdM#OwvC{FU;DQVOPOzAKAql{8Z(9F8E2S8x!tzDW4Csu)EL!)q3^0&E2hN4o?fe< zJ{)xZ^9wEcc_`8ll}S$KSf1!;V5;Lsh6<@By4sK3^~IhhI=6k^s_(*lNuyy_{l+;; z!1S#`XkA#ZOz?=*j~84Mnhs>LamPu&43YqYW$$JiH6Gsz8uIQ4Z>W~HBj27!e(>?( zH^f9>(`jQsGr|>ZvRj{jZ;ah$E}glV9+aIeXhj_x{OqRd=1PT9OYhttUtc< z@uLt`|N45P}6ys1NfdN3%K7+`U8dx>J`pVaz40k z*xPd=we=1huV|iYU4OWWrcA2Jgbv&2%{RO!2Maf6rV3&AQ}Z96uMeZv>y$DLR>vel zn#`TuX|+eQeR^!*e145O-Ahbm8AUg>(g%&{sH&^~ zW_bZrX5w0`Ta2-SBF=;a66=zX1ZE^aOoF1KLr4NdLy{%~#;sxl)HDWZ>(;83`f0Tl zEmm8ttt}N1wOY|?wXRq9ynC|MZmDTl{tcMS$2Wks>>j>xuIDStZQ|Jwa`a(k`X)+zm@fH&l| zh4-#6>U)Bf*l_FIysGftXN~Fmfp{j@+V1zm<7)HTwlyLgub=w8-gMz7>3eiQ1P=-Pbz+^fAhSQf3xA&OcrGLHgB3XmW&H^$UNrMwOZld z3}3me$uf(7Gn*3wC+_78f*r||End#uFQ85=4m4-N037iV9b$Ol=%{raSCcK<9Hx{bfG>QjFgkp=j<_j^xfMA zzs{$_%U4i1ly9uC*5)muJLJp8B5F6NDkx$leGMZ@=An?@n|C=LP}nh-jG~XiPI(C@ z%=5CT#5XZFc9i%lTtkwq1=?svmj%E zplle5_gs`sqkuiQ`ecu1ZTOUJeUkDC{!UgAutFtiVucYQ-(Rt(WLJ*u6}Aj7J61%4ib>YcyzR#wJStWqEwjSgXP3!iQ<={^?JZ{nEmZP}!l#Q({-V$C; zJG~@=LR5}gpr>~Wi*R=3c{x30u}83v0yIV7Q$@rjAg-z`-Wp#12f>Gs zdGx9ZH*+d`?^%ZMs3fW&*&N9#hVX4fG!H~=kCe^RDvNi7m$wTZKzUYxq74+CmE9d6 z{~K6zN*0THy2!Wv5k{zWFV9VjuEL+om*-Jc0WR0kAH1Zh3y*8WBZcJlfZ7he z;1I@?4Hr{?2>MwdbYeE5^I|x}llyXN+NS=7e_~X4cke`|GmKo z%iPT$mM_Qj{W4V_qP{FEqJ^xn+xA99NA>S_mmvn;ZR!QY`@O)Y&*OctCWE zJ(rlf;XRjVl)C#-EyOivwF~Pnq_7RhTe)vSZl&OV4KH)sdk5Y#44W)Oy~^&l zWc4Hd%Jx~fm%q*6J}j@mtgw^JWN1OSPk;IM zHyu>``^)FQMX!=yh4({F4ISK36R<1&1bw-W9)f$~0e?dW;BStOqnl>weSY?4U-9N3 z{5?|d3TUl^&q8TzmM+G73pJRn5^vc0bM zecOLcP`f3_pGQXZ6Se95KJUlA*|%nCw+6=iBuo2CAcp&&26f!;2(I(t;rCgbE_Cwo zIB88^<%; zwNHDle^i+T%uEOuDi`*(NI{!IwI?#^aoWrCQu^VA_WjC!ns$|Yyb2E zzBQot1hu;ZdRI{UO(1+(aP)Hl!0!a~zX!CB>Gt8Ees`AkR8W6i*LDW=K3!X<171%* z&+Gaxv$VBjXF)A+iT@JcCE6u8?FIj>7s+`DsE*R7G2`C{_k?|!VdWOgjgDg#8PZwf zaO2m%c&+>f6E;&>1`~%{SU1c~mW;6Luk!hlW%}1g)P%Zxqn`HZ-9haYpT0bZql3cV z2##JshIOr9@7A>&==RTk{dQgJ^Xos*wGaLJ_jK)NWLSR+=%}Q&26UVS_a6doqdIPL zP+ya!y%f~{N7t6?`UARl6?n{%^;s4>89a8X3%~SO9+PEeDlAL4Qd7FE zWa)NUP}|`Pe;OG5O-iiq`Sq2$_5-^8lV878*S7lg>vbFys9&RNKc!^)V?e(-OM5Gz zzZcLxB7n6GPX_fHv$PkGdb)O*uHUC?-==i?S!U^$1Ja{)YdPdtEG0k6E5}Z;U}Q5# z+sbvOVf)9=8n0VeHD=XAE%RXFa0_dVS1q44?BPh$WArLZI=h8c)783!b(6&{&#kh! z#p}SR?O9T)U+o)lZs_~IQQLj`{XuP~ProOq-RuwF9UT2jvgqIY_4{>gGu>Vu(4W_} z?+5fJb!vnj)wS2jmRAJzXS1|xgZdAH+FGg){=_;yLH(&L?OVG3jIQ0I>)Ui~0~z<5 z>5UuB5VbRtKs`mDn@STVZ<*fh8*xr(m2cF`KK-U3PDIncAJmrn!`B5zUr$2b>DO;U zZAG{5`SstRWCiqlb?u6P{!?9hl;qtW(0`SseGMYY&mGZwK_BXK6bF`Zt1FFR6JS z{cH*9>$0@Ipneals?oi;g>>DRv98p2nXI3l(m}|3m2X5-=zG3V@1PSC)c)bq*9Eoj z`@?qyN8e3CZtzpz=25y`5zwF2wQB>^x4Ah$eVbQF-fsl;r?a%Hg8C1F+K)-pKhn>~ zLH&s=?VGy(6zj>LQ^dM5uV*apiC_}fn;h~k^NpAtTHzb@N1xss#0h+467T!MD}$rE zNk|w}udc13+gJShTAeKFW?lQpuivO^zaV*EL{evwMSU31E+bLzpr2=h`Yl=7YeD@s zbl-F`!s{q~f0?noV?mv-pXQMF^J)*{A#aghost)}ug2!M(79nIexj~2+1JmQecc&E ze(hY#hb-;eLH+um_9MzF z^j&b&YEXYHi@GjP=-QpS{)VnSNcr_@#`#qY>Wc8 zl8`_3>o@4yy>z?Xum4KdKJn{!>soh!Y;7aSyDgyKpQZgfpsxsOt4Y)c=;sZT+$`;H zsIgFlbgGVTBY7XqSYFDTuj9+*6RsQhJ z!O^#p;P?5dviUXL{;!|fgq{Ey{51ipNS`4cK1A`((k>6`-wA3rlK7kGXGc(956y!5 z23GsLh;$$wp37K=aiA{LXFJmTTfPyA(3QSX=-#3d*~V(l%l+YNgQIUEAyE;bPx}kH z{hMEZP}i0Ps2YUSs5v*0ynO-vfh_9Yelw_jheZ7~{k#>VniNu_HT)i{iGED-KAy3> z5m29?&r8XRGQu%a8|Q`zTQZe>OfH};_;yfR<_}*P9F2ZEjzwj?&AaIKZC1_h^y_!% zT30~-k*@ug^m-$p|13-UAfSI5(7r_~-Ag~O1ob=7rALdvx}PX6KP0^#%2=->L2d89 z_Ybr7%X!%8mUQBniB6=+8sCT|p<8{U{^isE5X8ys`eQ+il*5~Xqo1OLc-^o6L8s)< zv>ycYchPtRsBr%(puehX|DrUxKB)gSOS?0u-xt&#qU6|4KUeGeTUpu~UEiu}kLmiS zy7p%s@JAV^Nd>5D^p2DcqAXqR8*z51*Ei}VpMGOdLo0b*Q2T~Id`)mP#t>*cSi`c8 zZr}Cmzt*)+S%vnkfPSa0Jwo#S6~!ux2Kg9=pcca^w)FF6P`@`zdk!HkB=6^Ry!g2vkD2=6!bZRC8?hjCjc?SOK7B0>8}-|Q+BN?0t-;ags^SQA z*0Fe)Za?1ZL2V6X_v7^Q57eCK#Rm09SndCc zuH8p@{zAri&f8~GeMIsut-{>ih;RA!`<-tT@BN^UdAY8==HGv%J{kgQB;U1J+IPts zpAAr-8GX#P=s5@U+p>6-@?ucmID$&jhd~=vPh+yvjA_% zIwjpK0UHmDngQao!r$UEu{0S)D+nZy(H^8pe+OIxN>Q;Zhs zH67hfz%TaGF6@1;U25DFicHe9W*sM#-{OPr7)^$XfFyOCLw?u)J0v{i1h#VU6QA~w zRNk3DyAiXwe1@qJF2$9_wD{*QSwWEt~+e1!Wy#(Lnh&KwCp-GY3Cc?3&I_qwkacFQ=D*DiCr_Ku0&Ji~QANU!9hnJRsI{K&)#$Y@%vs ztQ$`3BHr%zv0F-di|r?F5zD!Mn7o)+)^ zq;z(?`vX~BJZb*s0DP!vqYd`n%p*gfn?>xVw=4o5U2?&euGr*z9qUDX#mU;_4fpKc zv^;MaA-H0=zntH)`!(_&MMIP?d_kc1qA_%wcotsrav)ek9(cYZW;nZx7fJGeCvp@c zgFfC<0dcT@FcbCEzH5W#Th=hP7%qhK8q)G>)N z;u1+*!r+SKpC%EWrwe$^5rO9LKtQUb@KqxXWxH`)yg2&1XDvVMy=y}xR{ToxIHSD6 zwHkU?!2i0@yt#oY8t#=GFMfH+i{vrfY4QwXj`1l6clo6U+DKS>4l0xwofu_xz83j2 zzpkUHKGBhEZ)#b1{PBqyiTc(SJT0kD=CmwnZq8|GP3A1EYi?=~^kh)~l+NRD`g$g* z$5wopyrgp6U+7FK>`ubAh>ybOPiBXwcYWkra%5@v7yIR1jz(uL-cc%dpFQ%CAp0;! zHrupD^%ik@yYGD7Zqt<(n)|ES+a71c*nGh_w=Wvmv?_63q!^7cf%{T zp^2<4{vf=34sK|JEjGen?<$>xPfs{hiME|U2Gj(oDd{eI+hd5E*=;wvS3OtdEyFXK z&K%mB*9jAi^>$$YUwIy@C?65^BnZZcJi?||COU1kV@cOjkO!$^UCY@QBYc%Ti?Hnn zdvy_;s8z6$4-(Kez2-cA_6b!3ZS;{7^zc#}lTcVdijb_yV>D~1(_E5efw+Pv)Ra7(R ziB3WHZ;x2_U&NsR+;*lQ_4mrF2>;^IQuI-~e<$+b;j!i|W3m9UO@N=r`ZYeYta{2B zQ!8hc*5({JKL?e_)VikT#)h0^YfeXFOG8e5>*B?AEvfcGcF-f0Jqu9PPGD&=k}1!& z4xNAn|bv{`Du3w%FxJJ>-aJES4c}kdMu@BSU?WP&d&ytOWasQ+*yL}1ZQYtPt)jNNbmp`QrfG9?L(Y--q3Df^x>~3@i7(IEx!=@!CtFo)mRjiCA0MO8GQU>FO*}=g_1ghqG0lBhnL6F(?vOru9wB?%rj6p z_i-Da51?G$FUzHQu|uUB?|gcp0t zJ_yaK$eCSqtM|3_I{AFs8^(ASXXLO^J?62D@$RcKJRk3Fz?lBEdIEMry(80ncOnTyynLvjqykH@8aEm66@={VZ3bnPLNcxC8N1{C0O{` ziyAu|`G#{;KdLm(MHl2@b*1#vKe44uvC1hI5iWa}*u=WGn%W2~CiNZ0mMU0lV$5%o ze~D;eHet!tpWHuu<(m|`>#x4>@*@yEYav7{#Uo-5T^Hx=qluD~d|9@w#iA1Q4J6@-^ieRp;s7jv3MKis zw5!B|es2fTUL3E>tX*;vFB4^mI74v?YmD^RTO zWJV?#Y%l0kJxD8~w2DaJTxKq-J<(XtsQ}9JA~E0lJja|x zmU##8m&m*-XK9RNSQ-z8?%K8_D~+geR=k5pi}>44uq(9fXsaPG@;)q-ZtM%u54y)F z*XS}E-YP;1X~wKU!%MnX_S{88J3rne4Zs{j@`gm#)uNL5~*b?F{<`Wl&4`YEa!U^mQuSe+m72^ zSY*FwfQ4lU%ggdS@{3`1i6X;_PI>qj0G8*Kuooj?sYBiDgqJUa3gJ~p^8$pX=LCL- zre}og;PFOK+4Q7}63I$RIOSHiN#tkD3YGL+{G6Atirpow%G#t4Z*w$c{Wn>V&{GoS z3+>Zzl&NHUc-7%Xd75aHr%!MvEM47|lcn9%rQ=enOAvi4Ls^18uf4i+Jc`loUR~eeN~ZQ`I7&_W{+ws2F-ghFwrANf_2+h-HpjI; zx4X@8>8!oOy61y!bB1bu+ikvX*!y#rhF6U<+Mur*{kg16`g3e_BxmJiZ?xPlvbsk% z#j8j6Ih+fh?$Idio(mt!DF*7s8Q0L2FFx(fXB*gj_%O}aCDTqhq-mneg(@{yuP#Y5 zcCnIFx4R^LX5G6X8xK{=@w=E2V^enAg<&}QdFO{$eZ^>*ij0;ikYUSYm$p=RTX^>O zPVp*7pVzTctw&+nJ@Y;E&C)QoNANt?hNCPk39mZLC`(bJEPaf-v+8n#S)i2fj~+y*#U{5b5Kog1WuR-^I$^al6aiXI1WoZhrhK=I_APZA6L79;Oy6 z$0$?zMw$8#?lS4^rPh_`lrZY;4ZOwrypEaXc@&@BW2PbP?hV=eL%ja;x?h4MW5c9m z9u;VHE3{1#O}>SqTAwHSTKSZy73U-FTqFYtb-=k zVX&w<938cb2RFrS9sHPu=kluCp^9B>v~#LQcWw9V+|YK{G;9xl%TcCkV!b!!z-nlN z9?n@FNu!GH2X`W0aESB{_NR9%{%l(DCVnw`$IfzWBBymu*3#h=?Fb!VSlNSv!fCed zYNC$y{G7sm8o}$a7`bE0p<)b8JXBvEIfPa6~jeH2iEF zf4!;iD8w1-zIrpg?OyQ${s;-!J!0l>*e==d@~hY!1e*zAJ7wn^`T5MY-kyCp~*{yguIe%iKA=?G%l z)xYBJc(mxkHVImhLns3Kru9AI-H*^7+YNpDu|2j2;Tbtw+5Ha|nEe>m)xD{*m&wUs zmN+xIKjAcdAA4mt(uVJ2#~uW^e_Uu6@7ja|l0TIPB-?iLr`c^=K)Y>ECqbuJgd+JD zAOg2!?W@GGk8UX|(^6`tqMOnE4s?(@vUowdjjTP6GVlh(+17$&Me6S^&#)iNDef-M zD5>sN7qeOX?(+OOF3*m(S)S)YjdDNs(@TFtxBf2n2@gky7rRG};_WMD`-_d1Hke6E z>sW`jba=VI>K?D>?vc;oy4o!2y|4zJmi1!0? zDb;1C|NDWl(bIGyQ%`=Zr~0!#p|bT)CNPI9w+Rl*p0 zsJkr0v+y-!7{jYczTyjD;*!-0Vd7>sClbQ}pHGd;R?rIjtX^u4?+MdaG zF-Q+_swX~+SAuc!VDyljJT<9|&n!!DX%b&8n)-oE~yVQw^HM)6-LovH@jf|com;r(cXA{ zpva(IFn^|0qJC3U2fJkc(6>;&K>Ae7sfzQjoXC&5n5u+b(B8N{;`3+r8haPir^+jX zc3pkkGiVpo$5dKqeU_bfBF+n9-|I|$4jgCm^Nw3jR=fw_|D>IK2d(Q*tUmIu9i~hCv=B&qPp}Jyb=?rmpwM9Srz5sncF^2C;j;zEF zP6Ndm!m%EIWiK6g+>Ou7vV)JOvN14Lx3Y|}ko-shMhqKx1oE`^R1`zSzqG2VVy5_X zK#Dt7afV2K_AM-NR{e1BuTFhy%glbr^k;RQ&w6{{bO5#?=tmE4I0>TqYGkavCGI`TTA`# zUB_O|+|})cPudxVB5rmWdx`PU$Sx~Ctv@=en_b8LYtN{n`H)>U{VR8I`>7HYrY!?@ znetmZYjzoZJbhP{f6lIMKU2$Q&7PTZ+z|P&%h*r(>>0G^WHX;#r~WkN1FahDGU-2i zCj3rlMrRk59}`BWxy(sfzn2UjrgO1tCEw}=v~31FspSXqR1QrCNQWlcokC7c%UMDV z$RFrli(eCC=;;x3W=+{Fv4UyK9^3bzcTxRJ-^I0BALfHB-@D#L^`p5LwxELIWh-G{ z2>legQC@x_^ka)vE{kDb6#X!_V|=>URsg>c`8OxE82E+IrzW*_@rBT*qO+~BKABTm zU0r%cPJ3fp^99<>S+(>XbxrN!z1@CivmRL2_E^_R z{_u*Y@jBM^^&H%G(~gy%sbga+)`yo*r`AJXhvL!h27T z^gV8TWip5ND`Z!OzM{+#leFR+$z2<>+(5Z>2Tum%E&tTSay>)0u2O)Mi(nH}W74zm0HC=zjQUbfW^UQ5}*tA^6j z4mMcq020X0E4yza>`xqq9b_bt*$y%b!0ljTpHAQI-IsD0xeb4qi z*|)jxvA!o}Qa!@!8cIibiP{1MwBS#u0PvA-IQ%1DyWgWCY-F1#?377Q*u#`HovZ;sXoz>Y;S5=c>M8oZEcM$4O(M!}pvWrfoqjP|u_dmF0*CW|@%-&_9;ZYz6Q#U)^lsego9YxCxW_pTY!cR4eeRDy^h zHwvjV8uRySdHuv1RywVK3~EW8`S2Tn)ws*;y@}*i1i!@{*@VI;JobcxQ4u{ZS7Dm)_WpF&65I! z6Y^~6JA{5(=w~|poJKzf)6cnm@8R?U$~O|LpYq7_GR#f5V_B~4yE3TSkG@6*PB-z-RKqb)_ubf$&NGxnwlBnkiwq$#vsim#CuD&sz z;vJ2YwYIbfRch+uHTj8({#v3D$hhwW$Yp#aR$khgTtu?SC>BL4NhE`4S2ia@WFL6#57e^0G3kG*r|z0@aXz{T`trltfq}D2yyG-;>$$ErQoE zU_}@4zkdk%2eTMkP4ch8<1Ip&aj+G3%XEOfuA(yE8>}#NDCaut@CEu%fw*Wv)hKH> znngT6r08tSB(x0B^~6%)Gt2KQ2rE2xYZ<+#e4rm;Q!W#%+SqRm0r)q>AP z6CY7GA+OEt#I#UVr?ZimzUQS*Q1IS9jJ#!<4~N{hh&&i--fc)@Z6y6@8Vz%0zFkRe zfw^pzTg_?@Yy@*S-BTe^E1}9=( zb2#j=0sM2=;kS$M>C%6zBK-J$xn61Y7jqg8{WA=|ND+Rm z2%k>=+ZExrXBhuhMfmZQxwz8mFXlWP`ezt^ks|zB5k9T{ityVr48K(oemq-S%Rqlo z=pFiJ7=Do={8|w{t^SJe+cONmRS|yt0U74MnA33RpJDh#ituYQ41c>zcuKSFBK*A~LQ}t;M5#Um^8Ig2t#+nS`AhTAr8Cx<8 zuWB=LX!01BmyQ&_s?C@u!k+;eOB!92YF8=zSVf(?Cd2TmHe*YM;ZN>jXb*9Z(a>X@NX!m4f#{){Z2poo(_uT_NKk)izC z72zWXK?z*d_-{!+yZ&OH!J)qhpH_cG_;ne^zg7``M~30IE5b((X6mQcU(7!^^cUgN z~BK!^!K3)26SA>ro znxXz;Zo#2{hT)rC!jDJP>qPiDxR>Q`rT-LKlR%~1fi;+^Mm?CrMOC-GSgvJlkD#;5 zdhlWVMY^&+M$9jm!;`FYMfgl)B{|k9{2E30H!}>s#U(uHoOd{v z|BQaL;pZy)jS;X)3Vsc^Po*c!_nc7C)KJL`wsvy1wLOoy)W;;-dcmutAF`d}AzOAX z*R{zm8>BilR8q}kn`iC&yy+qv={Z;MT4>=lEfkpl&(|!nO$(LOhhkGhRn?(bX{f|x8#$a@RsWWFjeSV|8$^5) z`XT>ph5W>7yWn>Y?t>(o9Iw$zwCFrjJ$kQ8+e7kLfR#s4sWsE-H0KPcu8?EHW2!hh~DJhJ}C z-{8WZ^89we>kQm0)54bLa<0TIEAjfL;8o+r%hs3rSRq%5(7Qi!#l#$hSuV1lD+I6B ze%KHEb6v|^0r=H{ z<-^5-UnA~Qc?yA0C!iMNtVIa z^U6TQs$fO0KJ|*Mt`Yv^gzQ-pwE26$^|N$O|Nl(Lu9~2gAn0-t{yVsrc3|vBz;G~j zW~gdvXwH<-ym)BQ)KJ^3P)W0Sq9X_4=u)gPl#p9G_0dH0=#W21#)xa6@|TP71C^g- zdzeXJOkoX!5YCGA$X4ESu)W&_wR{N9<5A?cWtj~`t3 z;6IZIe0s@0^a|RyV)(CDp&qvEu3+v?7Nm4f;@39B{MHXKzwC*m?a=8vZ;1J=8)ANJ zbHfnoTSX1;5b|3y#Qb&)F~5?+q1AWw5cAtI#QY+YhECrGuv#YgZNj~*UmX7DY=4UM zuWUu_j^4k`i))(Ny^{6m<`!|eWAukMc);I@>d&MCze0iEqQHNxz@Js%+ZA}Q*j>(p z6?lOHuT+W(M}v1y<$VeXY~;Ad0~k79c5v_^cZOK69YbPQhm*?q$D2wKuB@LnG>M zm^Y)J;<3-L_GF%B!@+yqF!J83WccJ>G>p6-9!B0drNfsd7Y`%v7lw&9Rl5aKc$!50 zsj}lZ!=OCW=D*vvr5$_7shhU7jEAQK8)@%#uYam>egldboqw>-cfKubjPDy+C*n@$ zkJ%~axXp8QV-Q^fb@FhrWgoWwJByu>kq2xqAy+!y&nkGYSMa7G=OV%T&(K-cH|BjZ z<)JEnH)ddr13#c|3Bv7HfgfCt(GQ!$!Q6|nr+S(1JoBv0ls)O_g48U|RGwyUinz}J z3l=xi8|x@GAJ#2mF2$T?lrDKfuXN#a#N32AJn1ge{O>Y;+gudZ=KSSm-LaFM!rdU^ z{iPxtJP*z=t4otsG`@RQgxeTMrL}wAULIQG3;5`)5!(S5NPUgj>Qz&5gKL4>RyX_L z%WOHS19nd7Hdo|bI(y0!bJ6B>qwqIm5T4nzOL&r5b&)DT?eCwuy$@(Vb%V8lcDDlm znF7B@f#0jZf3CpqQ{ZG3&MTn($^q^R#Bb6|Ed6We3S3MH^YeOu{Z8QX@Ru&LCIRh1 z4%&VL+HY+Le+)2g&Q}3#gA)W2(5TnoyaL)IP7p}IIBm%KI-osn1;kT8`@I5xQh`6^ zz^UH$Ybp9{K|wzBgv)2LO}e%TT&_Pz{5Z59L|?+F_!_jhgHo$f%r=I0qK>M@2wfqfeFIk@DLqK~)-dg?!v{x<9@*$vYk++t= z0qu3mvwR3>Z^&EA-+=a(cMmu(5N+VUIFcWCkP~<{ZoN|puqp7z;`O}j}-X775IM?_$LZn zLn&}x0nMkt0}7l@vvytqElYuqP~f8!_#O&;F9lA=Y&)-j7FOWd3Va_0zOMq`Ux6Q> zz{e=?0~Po|3jAONey9RJOo1P+!0A+K=M~W8$#2$o0qsZy{ZR^htO7q;fghv5k5%9k z6nI2|M-}))1zw=QCpqwKqFvZ5l1#Sq^cc|S5MbvO(25-RSo9-VdnevdKaH*iflJGh z`0WCh8cX~gfyZoojtX-A^K5v7z&maD%>rL-!(SCRwZnADcyo2mf4vQ_6Zi`@{09Qx zVZ&b+_(-Iyng5|#oPUlDKU?5MHvIbnud?B<3f#Q^nc~Ssdqe5J%toIO_;oh?MuD%h z;jat)Q5$~jNX~zY4L?udJ8k$K0?$T%(k0WG?gQHKBGu@LUv%~m$xn7fm!uy%isL0V z{9J+0vEg?Jyv>IHP2iW?@Ckcx{%dS_i@@0-$tHR368L5tJ;@xD3{%_`J4AkQ#?xqF3*;e z>HmPhue0&_NZ{*i_!M;HDIXuT;avjXV#7BGe5VZ`u{YzM!NJ@6Y4yw8eX>z*pPwYXp9~4S!zX>uoqm5YSF>$Uo))9`6gT^py_!YXtoc z8~s}XABp;#E~yvY2eg?EKI1?@`JUrSU*(`*F6fJF^nVa|l@0$A2F}EPkqzhaR6F?7 z-g}~7W~2WffnR6Cw+ein4Ih6X=kuryZx#3!8~!VS@3i4u&RU0@qrc4gXQLgX%bL!o zIq1pfjr5}S(@8(aK~FRR?Mw%Li{Mk@%154<%%WgF9}4<8uJq?P_{R_8a<;kB%idm! z{~dzx;Z;B5-L zU4eHf@Ffa-sREb1@Kk>B^tnJmPbWM(uYh*30{^-Kzf^%=rog|Uz?UoV6$<>D3jA^f z{w)Pgr%XGqfYz(PS1E8g+OhHqXx~=Qe^-HDt-!BQ;MXbe?75EJb{09pBCI$XO z1%9&vzeR!Hs=#kk;Br)HPye4N=;Q@}VG-}(yc2ns&GRp4^;Zs+rmf_{Sn->ASJQQ(g%@W&MR?-lqH3j7ZW{3!+g zv;u!dfj_IjpHtw^D{whWVo(1U74$DD@K+T0s|tLJ0)JhBzoEe2RN!wb@OKnAt>!o{ ztRpD!?F#%|1^%7_-=VKj`^0RNH4g%s zUx5b|xURrQC~!IJXODLe1^u21e6#`&De!CszPAG3SAp-RzzAv4egKljo<}<_Vlmj_58rjLR?2ZkO^uEO7JLGBSpM z_EiUedWU&n1%8qOKUsm7DDWu?yi9>tDDbHYJf^_o3VgZ(uTvD1+;nv-k`u2DDXuJ{9FaztiW3pc$)(sokRLe za5Erim@rfee4eg3-gF0vFSe#`_}#j%5JLHJ0I9EEqi;n?t|v(2wPsm_v{6 z$WMM4?}`O3rtJ;cDShxYK{&4uCzL4R!IPeyRJLjF2F#IPD`imKUrvvX|xO2YfTMT#33w@j6 z&iS9~819_sxryPw_K{VXuOBhoId5|(!=3Xp_cGi$5A!RAJLg*-Vz_f& z+&NG3Jj0#yA+IvrIq&fn!=3XR+Zpbh$M^@so%0nRG2A&X;mf5|eA{PEOS&J)aOXTj znBmU(gaa7voHsax;qN>6=P~>P2Yw90cRKKVhX31v7c=}52VTN(=Qw>D!=2;tnGAQ1 zyJs`pIlkuSDmur}^^D#*UOt!M&T;YA815YZb~4;K&b^f3&hhL@hC9cty$pAbPp@LQ za~yg-!=2;Jn;GsLSN@pc&hg_qfuCSAtosB$-iH51;8<3-TpJng9LGJu@FM?LE!3L! zM}ga5?L~nL_YmXt>jKX=5Pq{&;24ftuJ;)3w_x-z>j?U__ujB)f#Jg0fJnsE#qg2x z(fE5C!^L3PKsD|63>Sl36aFT{?Wx8I0$PhxP`rY_tBFJE7V}rB2h&{uA>6J2-^t>5 z6>v!hB7%RcgO=m=-Zm!-IC{wvccu^L<4hlp!@0@j3TS(|pd=@piCnILX7BxTBKX<5 zP!k_G-ro8k`Yau7C#T z0+%bGVR7E&3TRkVcew(_7p1K)1Dd_m@%I4@lPfM)K*J=8%N5XaTu{oF!xcCy+>Unv zV^@mx75ss%fOvvGumb;z0zXQD+k5AnETD~3&>yY9k5S z1wLDWpQgZ1SKxCL_!$cPOa*?H0-vkE&sN~)DDZ>=pQpg<6!?4vUa!C#6nLWoU!cGj zD)2=Lyh(wdtH94w;LQqru>x;V;H?V0O@V(+fwwF04h5c6;7b(vQU!j#0`FAd7bx%x z75GI8{9*-ui30z+0>4y&FH_)`De!M7@Gb?uT!F7p;42mQHx>Bh3cOo^e@lV)DDW#3 zc&`FqrNFOL;Hwq*w-xwz6!>=)_*Dx0Y6X6c0>4&)U#Gyor@+6jz^_-}Hz@EM75EPn z_)Q9YjROCn0>4>-|BnK{MSHGvK=bjME4Ij(!-x{hGio25;{yPUgm*E=>T=r-m0e-kYwazHJ!sCM=aUn-0=d2KWfO+=E%^Cvdrs?+wA{cM3i`6!;-9)Lhef6tYKn9&k6k+7e5kIYp~s}g*U!4yxf z>D&y7pU?2OjCf^Dv4i1T4ZKR&(cFA@d2UtU&nWPhf#;ge*^u?vgo%W2H{zA)a1F!X zGw|PtyO)4x8>s1~z#ZRLfp-AUHJ!7uQLy?s!#^+>OZ;C9-|4_hp-8UwZwJ1N;hz}z zQ{wJ-4EG6Z)r=EZ{FoZc)q)N@&hQZi{-WUXZHDh*;L?JhWB6zTm-L5BCjQwDd;!Dv zHE_x2M+`r}fxpf010DD{R8YCv!4CX9h973&Ql7gQo@?MT-cK2Rq=COI^gX4R_>Xnq z*E9SW2mTtvCphrKk0(A+2VT$c0tfyh;8ZVFjo^a(M9A|pa5p>rSb-OO)t$Z^c&_QZ zg}5kpe^k(Co#4)Ai~>JifuEwl>wzDxrTh%X2z@V5(63P7cPsEmh`&GOhc;61f1BYc zKR1bw!c^?x{*<4a#E)fo%Fj*WmB1;#hSm0m?p;{4Z$2qpi8z;m^bgZ@@QzeCW^5d2?e^a~yG9CWgK{?-5|z2rVk8SgiN z=V~cGd8yY1LBDzwk5JO@Wb}TAoP{Nv{}vnlBH*aE9diDUpqKu%GTmNf^r?LTQl2lD za{kNq(AXvAsREv>rTo|>{VGAf*+%~mqkq-Mxuo*t6G5M|Cx0X5DVjq3Uw6=V0H^e9 z6ZBG^+Zg?a4*E9){dybyfn~)183+C8z=?nMURHg382!f%`b~mWT zaL|XQa{fDP^rr&P)!uQ?UoGfMLRNVmXY}tn=toTB{8!uPPXeB+jr4OaDf_uZ(97?? z%JjLL(d!QScNu=X5ifmnh^|XxJl;r{KcCM3XuklSt6gr;-y`r31pR6o{e(Et%U*oy z{JmD-lL3~SV?&fKZW>z+d*FsoY|G2KSRj*eMZ03LH}n#pS?FH zl=RutiGPQKeim@z-)5s<$>`5@&~Fs)q;NHOyYlogFX(N$+Isf zl=jxm=)Ypv+eX3XaY4UK&`Wvz2$-v-_T5PODZq*UWr4*G*@IR7;VSmi$r zc&>JrgZ?@}FI_>vmR(OV`tc5ZL$#d$yf0b#R|3z~?soA1j-X#}qko#w-|L_sGn@0z z5gkC8KDEGewfh|O*9rPIL2t;<=pS&J1HzBy^n8U!HzRbS?U%oXw!1JBh``{pG5 zg@Rswk5H!P&lvq94td@e^wM=l)>q?E!RBg@Ip|w}GkFf;f=GRDXY@}v=>IC{*V*U~ zKa=>&*$d4$iFF=u;-7u6mH*9*{wW9l?F>K3!0Ef;bba$I&ZkZAY2dK70eH5!H~$%d zgw54T3_c}-&yB!Io-Kk;lL+*@z^e}7z(RqKIh)h37kItEX9+xVCjds5{|4Z>TDc)VeTSN^vk{Qwm#$M^|odAI*ym*@Mnp)34D#f=keFtT7hFw$S#S$ zDe!p$mwXPMPx74N6Tvn5wlH0f0w;Oa3;HD-)=sQg~ z^5;Gq!me8dF28k+W{X{01-{-!A3c}! zsx|bY@14^1I&g|N=P3Ss5&xqda~{#3X3*33N9o!CoXIKZ8#%0f!szE1^s;|`Wi#

UPd2=V<;c%lmV{bF~J8Ue?d&c8GWdJTARV=9zlP2lJE;0cpbwp zcHlo`_@xf~FAV>N1J6N4l&h_9;O8*>y9T~V$nzbBALkd3#t!Ozm(sjhYQLw*PVG2` zR~qzVM8N65DPQEbgVBv)*X06VGoC;1E%5gQzFy#xe*F0&UyilXe-}9BIgNP#EHv4t zlhbEU;PkTmRtdb#hOYvitEKj9|6Ry4>H@-3`?Y^5@KYI{+Luk=AE4`b;BNVT@P+R9 z@xWR7M0j|q?}Y-F-$a%81HcbA?bkkz2h-kVcxu14lpzNdi@X=4?9Wu-hifMr=~f_K zex2bZ1}^jQ7YaVFG5RS6{XT-v2MYSEiz(g;gML3jUvMJfQw?0kdnlui8Tf&MKAX|U z4P4THmC;Xk;HNWurUUO__$&v0CBsiO@IytscQZV-Ut7leM~0{NYfJpy6G*Srer<`r z$LLf0wf7hNpJeo@{o0a#wO{)TLEp&m)P8MA{}AvTL1X#T-q_I8k*IH8(vfUz zpHSb{rX|jtncs1KA$x(}w&n{8+v?go8snW66*FrhiK1C;$)?tpy5@Lof{A@ zQ_I45eN8eNtzT5vo=CRWH6=UZ^%XUB_@OJUf>YcToNQGvnqN)nTE>&LEWe_9`atBF zP~Y0zFd;fQYEje5yr`|It+BbOrIE!ENkk(xOwH4p8qcqg4Dvk~M59QCmX5l5%2hWe z^Cg#wUR)v_OXhdfw>QmiY_C`}7=pxmA3B|fuYqc*jsd_hBFeY;m+Ppezln9M+YPvdK9@s8iq>QI+9 zH@a3AqR`AlWT5k&7jm2ds$vVduBYm?5|)^8YR38u&|@PuD!t{`-{Cy zub?9{@xAOX8b!<8(y+L3F{}0Gd9%lvcyU4jV*PHU5%lpAR*1c%NC0` zhn7yaq%`sHG|9TQw#F9M$|1!e>SdIa9UY!6go%-tZ5Dz{I$fe(wprNRwxq+gi8o2$ zX_<^cn&gan*=8}-^^A*$d6=Z}veUx$4Ak*7RK_5kI$kzfut?G4G^yZap_3P>dyFO) zUItpQ$h-TIVw3M>rIQz_dl4oUUiMkgQP+^BpyYd*XEf5$(6nGdN3zwu2{4J_Wu$0J zn_4h{&V-MrsiG^+8)Wb5Bj3wL3p&~x7pDpDWuj1KNejk;nJD9BsD&4zf8p9aHl?bU zr50S6UJ)-dEkgLE>1CMcWuucW?5Iy`8xy@uw6JYKS{t3{WuxdGEpBXIn5I>l=w+Xi z+m_64Zc0}VPxLa)g3h$n&O|S(j23h*Y;RrCmS|{1sc&`dYNYI9qL+bAZU+l>rgVx4 zi~NaR)>(w^in7<)DSHbOz3j6HwIIxEq1Qk%#mUQ33!5=#<635o@C9C08bw)aT+C#d ze}T&4Q>;AA73tF2#wNlFr!*yJwYk~uevfFrNbF>2gBaw)lYK=x_ zw>8uy8*4EeBPJ{J3q1`zGC#xKoGihGUdBFok$T(#g$y2EMm`xskB-K6*FN%lBZ$ki{=q3HK}^ECSG1v z9W64N+U9!nz7Gh<~{NDkWcbO}qGDNwl<75qm7$87Vl3aKU*s!yU*wDCd*yTC{512@ zGA})s^9ot}<}p!WaIu!xAxk0@ltCnrmzhq!5c4pL>%1oEM0uL*U2zbbrWTJhl5DJX zhb@U@?Jz()qI4Q~LU~p~Mf20Alwk&5wV7b#nN|j)N!o@F|e4u ziLl>%_8Vot6WMPe`(;6*EKHPhVxce#1TOiE9@8;&&qC$1crckk2n(Cf!sfHE`7A6( z=Xi?|HvO`&80peI3yUEq-E-MlSd5c^6bRY4Ko|%Diwfl?a!oj4R8V3ZLnH-U8;mB{ z6W6MMb1C3l3PcidF6gZiC3>0c59fkT9ed(j3OScT&ZUrZDdb!VITv&dD8MAnWfJFt z=97V(3oM2`aV}^N*c0b6iE}}%Ng(PM_J?ypSz=F|%Vf@FGUqaxbAj^$BjH@I+)Gbb zxMhDhmm!A$fT#7iCBF?3VbHTbFkzfUn{o!1SIhSJ21xs7(9p{3DCHBO*V4;US zF)p<7f~S0}-?KlAOMZlf%a1TF`4Pr5AJgGP66NorJOE~<8OSN4tX8Q>=1)x2v?bWs zzp)|FmTXTnwX`+Y)i=gdjxQEB0C3sF%yvpA7h*s_E2NZbJZ%Y<4jUU%qz;>^L{xI{84m1A<@t;;XYf5QlV%F5D zH5Ijq+R`bN6$vD0Lvz)zCSEj>m|;x14R1^ZhzWCIh^-m3B{b%!Nfwi*1xnb0R_IFZ zJkeq*ev9kQYfN;&2@6FqS+}q-QIkkERV=M2Owc1pYiksw8m15H7q^iq+AOi0n@R0y z_7@dI3{e_8>l>N#nxM4Pl?yva@iqCsp|Ll+y1^UH;^=0TM;#9Rh5>~eNH@hRDoZ^i z%9o+H<)tH0c-_Tp+LQ;DFyiHKRS4Xso8Se}(TJi|e)@#=#sw2lJ9l;_$f1ec6PuD3 zB$h@r=88DAy|K}fK~a~A){zJUvvJ_V_ox~jFc8PnIzjdc**;csRW(OJ*i zqc`e#dz{7#dkCu!(tte^ZIY%G6s=ZSbPz3!$qZ{yLo{?ystJyfM1C|C8@&)Q%0a0! z(@^?nx110EfJAj8&4pNHw|Z(0jo22Ro(`H;St2QQqeb1~;qv#}a zUlwl8jdAa8s^G*cAX|D^(ix*=OoYi&Wp*mIq9)}Xtv8F_dPDaN8Q%lXwt@Y?C&}=$p?dVO#Qgf#p4&5!S?ThQ06HVw|)G;m~ zgQG9{-)*yM6B=f}XgZhv*0){2)5tBt{KmyBF%$DKb8E!Kd9Z|)ffhr?#R>_5raY8J zG#|N$S__80q&^w1f=B(*+CE#EhC&*fYx!aB<3ETX4=q94d4UaZFwlnegazRmr+$ zoe3XTS)#V(_yIE#P8gYlcU4Gufebi&%IzT&7OwegxYT6A!BvUW0&pfQ8r(A0SpUt0 z19ed4nh#nnS`l_wThz7+p9xmw$7S=E`~DJcr0V6B6NpYqZ+T8 z>e_qaX;72euvu{m`pf8R(O3$8HeXxPNDCBk^Nv~;2GL-1ZMm3dMduH}XwZNT5PD07 zTG$L~OrR7lXiU0qw$o^zY+Yi0VfRC{F|q6*4>%W2IK_4ZmvgpzcS zF{-xYI^yGmgN!ZRCSFIZEWqFmUA^zu5u>q3c(AIfHVZpq6x)gi8Q(1WavKhr_&<-+A<<4iPj(m^wpyTu2DeMw00t`MY)9kNyMoR$( zAFXOjwlh9XImpyi@8WhuC`ktyW4fK+j(AysK__gco5dZ`TLKPR4D~*9N5qnJkm>EU zH{B6uig=K*DtELyq7;;ajIaL#l}7L`Hn;|h%-qB>s*z!$K<`4 z-x05ge6TTl9s=NqI7L0!c+;F6;E35wKiIgv4jphrY!DARR&-S`VeHoGTTO!vR3!1B zV^!~VaEVnC4?5QL$09frnup1(uC~f04*T-zEQve<+Gb{8?c+WE|91%oN8ffrR6sJ3)#UX+@#NgxiI^D$)wICjRteKn%vdrT~MF-TUAhN8ASa;A8eYFvk(Gq#k^{ zY0lYk#GIlZinu)w=y8c#(hojvuakWo5ewqM$C}}JKn?+%;RY{D|EIq&LhYO{$Ewwx z)mQ}_o_VmeXB{~bC>0{Zgz2<72V2I={ZE=sIW<^QI+UIaR;f}Re@>5Qn}45e6pnEv z^LF(Zk9snY4u1WN*TuuIpB&QU1ZB3zflWo2S*gZW1h@v{-+-*O$koHdk}rkGmrM?i zau}@$==3m7qt;~W%z-Vh`#k}LQ>nzfHjH-|?bmSgbhCk>ol|Sz3Xuc%P&pM@vON6> zU6#xqbPmZm!s^c9oF()=Fw8?E>3{2zvG!>$TT)<-%=36Kk4QaF1@jDtNL?qw)5zNI zf<^5mZ;nh`1Z4Z!Ts_b#$=&JAVxPlzUYR9fnfDe;%}iTeM{-#BagU=Y8uRnpvDqmB z*4QBhpFfY?!#0B&P$tPd8NQW0vxB)uBMb}lst8jq%+8XQCTN<5)Md{B%PPk)ZhW1B z)+@+<@n;w-qyZi>XE7-<(G;Id%+y_Pr@!+M7#?q?C@qJtrLwv2yBdyl2ZB{G+n$Dj zgin8Vo+Zny@%P_wg;9z%wPCc+G^hD_D5VTZ{`&l6G#tJ{=dXA$F?{1)^TfL`^9b7? z5u{uD03=9re4vNk_WcS2h}?aj!T=Zz$U0qi*%>WR168ZTH?r>MnT8&@(0|Pvs3@|Q zgWU~N*F3eu#+jXT#Nv>u#0O4I!!^w4e;Ql%`xLVMKHRkE6JEnJ7BcoDIn&!B4L^@^ zNRyw}^weM*=SRwdgjd7a!S3ggnHH^Cm&7=7Gm|a}-)&{l)53AEEoQ39cb>;N8Rv0I z6RJk-R7O3%>iNUhRUX{Yp_X)^l#Uh}=Y-;0c*Psq!b!1mQoa$}Iy7njAP*QB^;mEAP78YLV81vw)Gz?eW z>NxzuS;wMD!@nV(&F0z-=M=x!dAJUvki?l3T-(GxRRP9{pqaTrSj#r6t;Nx#gA3n; zFUMu1fkS`eY}A1=;K%e1Jap#!6s(D7*?~%DuEbBFV|-w+?8F!RbsqX+-gY*ib3xd= znjA*4Ly2sgThWF&_pg}`&@-RvMk6ruUQYWv)4o&76kPg~kuBM5t(DRSX2(#Z|LOqh zC9e@=`g4~(WlXc(WuP(++5j+_((mk7Wn%{CH+!TJ5AAsru_ZKDU7i^eWuG8U@B`fk zazYNJgWUa3-z~1HGViHor)e3xrahu&>DfO%;_Jz$3`A;_Up^fsqbzT8`RKA+86H4v zbg+`}5*7!H(ZNkPkCUIQ)Y!5xxyb!!C2NT_s%VedU0dB~sV|9e;Ez)VF{XL6K z3N(;#R;ehPI-1-COBH8h?>$S0)M=%yrbQWN3FZzt%2-qfhAysomL=r+S}U@ z+dGpW(Mfc;98POspG~1Ny&Nr&JB68tWtYEIX444)^kNv#Pb9k=o@1wRkU|>@C=PZ? zoY#o6o!G%>nkK&=AH>JrH{h%FAAicP3i0uG!XGx_cbM>vCVZBUzaaWQneeks`12;b z&4mBbgm)?MYZUk<6aJEk&(kLSWfQ(#;73EwS4_Bud%8&eS55dH0w?+d@KUDFaudB- z{_7O@9SXcMK(X?8&GMgS!p-u3P2hI{Ani(& zDVgu*o9I6^(XTY&2f`*L{go#C%O?Cr6Mm2hzenH{?`p)m4;Ne8WTOAr6z?-8Tr=@` z(}bJTZNHJ6A*J&%Ci+7KPUd^C2_I|1&GqN;CfqE4or(XkCO+qx=*|4UX~NBX?lti_ z&cx?8CVDfUM@_g{&KFI%nSXFpDjlLG{vmc#cq_{u|gC(2qENu$oif4 z=f2PMoHOV6+}mb<%wz9^`F!R%ulMV`&-Lbnd3Xf; zPk^5OqrrJxo&Zk&S;`MG{sZCv3+U-TADsC=08alk$`3OBN5lUe=;^-!oc=p>Fa~Cm zy3s#Zx%d~r|48WRe;PP_ro(3te0~BweP)9*&tHSne+B#t;r}A^^j`~3|Ifhb-?O7s zCnH1Za4h@}RqpPm`QY?F9-RK?!hb0IFNU7^-vmydhv0KOe3n5^pVz_Z)1_0YyQ%)l zxsyGP?5*7GZ!d86_Xu$Mm%;xe_)mhKdDeo{|7vjh-wXdD_&*3e{htM=&sIBbs>2BQ zbWrZr;Q(;@jDpW7_?!tn^Qi!5e=h>3|2+81IjtS9d!VQPVsQFD4o?3M;XfMw|AwCa znL9;wllstq4{-XAR4)7182FEd{ð5-ga1*iXg@IM1Si=n6gU%=`A6gd5N$=Wne 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

Tzxf zSw9HX2=JW$3OR$0^RkSITDzV-Gl@j~vVh8b&V&~2A z)^+2Yua;Q%j&m|4)_2A`e;?0!j$&6)E7QSit3n^1)MHuKgq*&R^}CSsFVRmQEV8~* zzcks|H+lT_$&iRtz9LPPWz~c}+~)iusqppI~Uip_uN@HSMQb@0$=fRo z>cVLAlGx%%XLm&2i=4VW&+bUBW|hSYBu_%U*R5+`p6uvc7fE)kZSU+&Mj(brPv`Or z+LJygE6i$5N7p_m*hqJK7d`@F9oKVeq@|^+vnNuyes#z4)fC5Z7xz|0PLCv4cl1m< zy{F^i_R6Y2D$alD`p%CnnI>y8E!x<)c-q`gPg@jS7^46fvhBADjLL?ItnBVw>sL;4 znu{j|o{JXS&;tf-S7%Df*6J zOmjHJ!ut@hmgo8>bFiSt=lMY|JgK2<^vB|j9N|v9#U(sxLG|d}>S-`AsTi6~S%X!~ z^Cg7O0O{F|utkTEmLHD&@oPBvH`8ZaW z(Y$z6!hD)b@7If`ism$z_1@04U>tz#!MXS|L@R>k@aVUE&SV5j$FKZqd`EZ~=U(2F zYqA%XaC<~#VcD4H>~LSh`v|ygsh&LpDLlFZ4IfOFk{5A6ZxboD|+pgF+arXr2xf>fWLOfl{mTn18Mc%g-tiGH3#py;(JUoN9-nj zshsJJ_hEZZ#)ljBGJ#8~@8epwn2Au|#lkjH0_QQ0@y}zTo_H)&Qo%9w7xghrJWF64 z!yM3bSPDZTJ+GW=zUf6^Z(bR8Lgqyh-h4dZGY>uTDAzj^f}4+yH$Q^6cnNcxsN0!z zDY_5fY@!Phr-sX1oU=*K-|u>-p)T_(Ng3A?SP@=jHaxvFJg-b8iHFdJRt~tCvqd@S zvv4`Pdh~ltH-dZnFV{Z)3;vf+RzA#wh;Km(Xm_e)rJHWSo4rnevB2A()G5IoI4qQ? z`irn^zosS2%)$hESng--4`05D){d{Uz&i<48z2@B;n-OtZPj2cA8;;nJ+21;f{ki7 zbDCUh!{Nx}3O*NIve0{Wy7(O|T=9F9}BE3^A8V$U3BO!lC>VO`$eQJ(jx z%WXryG`DIWP7~DP0nWT7;wgHmgGPx^SU$G%0eLRWd~O)}#6Ax}Xe$b-@Td~kK5&KZ zz&ay>MZ%nuV*~E?FIU9*znGiES6q(ODvT?=)40`Ci+1Ktx6%oaWW$em!14FPenS;X zWKMg9N%5-J;ya$TN9uvWdj!$#)0aiWz>@8KIP`gl=d<;Tb+>?+7$Rd-7&%1pbb&ua za;D+RArh7#+y^&b4v`xW;S>3Ci2SQBBlE#iER~5PK^z9LX`ZNiF5Ji09t*iq>zWvg zh;fvTyT9?u}$G9wFjFLz8&MFw=}x?cKnp z!NY@#7-Ads5Zfh&SZ_fk9y(!lvjc2azvMWJ)0Fb8)=KE8*pCE!Sw&ZP(z(^6cdGFh zMdH;DWuxircr4zV13pKZkSZ6(%XjZ$Sgt?hVfhfsFoxwrz|I|(4<*Cf56f)Y z>{7TMbLB!-&RSqrf8^?9?y$`L{nymHf;boYeZt!z_asxp82Wh>{p~9WGwbFQx-Y^c` zlV1Ptk{vZTjk%y~^ccUta#ZEZT=3FNSc@GCF-$bN=_rPYL^|ZY_6pvq^$@gv9Bgy% ze_>E8Beu5yCy6i+*Wn;|3Z&y+;w_NDnI018{VmUB^DO=wYJnNAI_AiJK?w*Zyc2Qa zVRV8z4}vG_7o;2CvKHd-nfuAi;crP+2a_j*Gx?;s{mFFkfrJNXHSit2K~F^Q?>{nf z8stM3Wwx1ozh@lwPI?V;=!0L8@G2gL^+837OGkg0$XrmZ58EvCo*TJaFVE}sP`pA? zum7thN#&u%GgzQil9yIib{j7jjrB2t_Rru4{I&^jJud2TjYqbZmLxJ=IFl=~;oTsx z3~dEwI!J5sWt5C?2f-C_TES0-u>c491e0~NR*WngCo=fYS`hP$XYq5b}Xa}7AUQu6@)81*7!bq@DA*ZXgj8r=?Eh7#W+r3ZX*U^<8oM<<5q zLqqMz@oZ(a>7a{|c>|JPg!wyW-5u*zMZ^@Vt-HJJB6A|95=}VdgN=*+2f}@CVwpxg z45a+&&0-fBZc~Jh0^#JqQ0l2rxc@Sqx5T3geLq1CaaKKzrz$WN`xWo~1Pmvwd_I30 z8v!T0L6e#rd$OL4_kvlYVoYI1mMZezhMLEeX9f-aQW&YE*XMY!(@ z62Zgsf@cg_UA(y!Metos;OaKHwU*|_j(o|0o1y9@v6K$7tI3Ug{U1-0eoj!+51HdZ zQKY&a9_{26tdvV-#sQD5a^C`Jo0x`&PPgF7fQL?*O=>MSI0kXUrTl8cl?Qkmz~(F1 ziMoj}^cNHJ5^y@_v01n?lv9T`#mq}_Uy)L+)IBBBpm%xV*yYM^1{gZ?r)ycY~A^j?}`N(*{^mq>W z`!B)cAxjMxjeH=i-+%Xhl9{gOXX2SsiY#1poshJ6bw)NFt_4{!x|_78cwGmc??sB> zUbqH|WjfmG6&~2b&vcKt>XkCYRycCbo>s4f!nSwLYoV~%z>_z8#Xy5JF2?S)C=)Y% ze@TMztn33jk#fjYp38NDVabAaUND^UEx<;nmqaW3Fi=EfVoKsUjQ=r6;!f0YXp)^Vt!IcE# zgfnjIc{(%ub|qcpA}&Emlh?kco!^c1*PhURc&(wodQhJ_1Itd6*lW}CEBW{ctp6G} zby>tpwsQ>u=lF2+YnAK$wdV0ernh>uY~?^4HNv1X>rp11sh6JuZ#mB46jLT1OU*WM zYA6)P>nkF?Kf_>OuQe0zZTYLAhFSyByQqYmzoC`&sVUvIFib-Ec!*5tR3Xh%2bmOB zc0J~86i%%d`1{;%=O}Y#i1r{C!aXTaK-QTY8a;ygH3e&8qx1L&Q|6)3M85M+YL4eC z3|1EW(+QlA`YUA$BU^}8E!mAk`*Zyy^?U?v-y6ju3LhEt9uj>@oV~-S{Y2kjuX0yX z^6luxc}3a#G+cH}@2Nna%&zkpZ;DqogL1+dHD6BptXlqMKK3Tjr6x_u-=aYN$7aEPYz7AIH)5ZeiZ#-Df)w0Sbe4D@r8W0Xf(?C6ojLy9xK6W z#IvYGNC4t>=V7Gi2UR>^gPw63dIsqA0U+pc%>|k1fk9g_YN|yT7zUt-`LEe~Z=ZTwW?p#D%#5r<3%Gus(4hY+irj$-x$EKZ+TRTZR_5<?)DWO zJ+s9QTujxY&?SqX`_=SA!C%a+rFSUBpg%KbYw(>IE5yM67Wn*zhEpSz_&9u3q;~qu z={1oV)irh1v#U>uR4&FwrrVa3@M*KE$np`2@6g4ItxsR?DQkX|$0Hv-wBRT+#nT>u6o~2yQ9}L0F7mF(ZOT(OeK&NFtQa zX^<0xu-mwjZj*w|V1meVzRqG*0@S6_#UX^brDa=-=9iXVZo8$CRB=mbIdaSub*C(E zA`Jwty$c?Ie`QguwET;9tTd7-o?BYE)oCoPzIDqJV`*#AXD5`_MoX)srIm9_ zBXdj3kpuLi6D^kayefMsiHfqLEIZ1wH%`!nFGBUQeAENATIIU|am~jUi$Fa?KDHJ& zmR4Twpw!g3qE!=0D?!gJ*%D>zZ@q2=4a(*6B9xM{8%rZCvSi3KKJg+y9-C+hdEv~H z2%qKO0MA8dWf|>4^6wSyIlRV7%f3iiW$b41=w^BRZGgwdiJbw+C-XqM12~W=b)X)r z0t{LgT*Q|s*q@jn+6-;{MH?-m+6=N%Z8jfm7R}pcjT5aE$i7X{syYlei`f2)NDFn! z6whP(&m*lU+y9fLw}$L*hVoLIJJGHjS6VW(=l6fO=Ux|xsqvSO+v;SBzi3}sM4tJr zTZYRhJHhjrxq6o6xv{j#elCA83ntP&EK#(mFS5-Vsb^^G6m+GU6^{5ihK4=~XjiFv zHX$yuXLV#jX=JUh7ZAHeoLvf^Z7uKx&(J%wZwQ`+Z048l57|c+K{jAuZ11oH4OS{Y_W}m23I4>JIHm3fOwv4!(lRp z@1tEcFRDGo*cPL0%l6+-mJSr#mlWrfXnh)_L_LJvf?kTg3!j5SDLSt^5jL}bw*yw;hzfjLFkhO z8*?RfN!ib={Izdke zvVp9=1bm_S{#dx6wBDXs$n32EKgEjQ3$(7}v!ii;oW__}^ceI2qitaHm^bm<0&p>Y zuTkav1xyRR&-8t`eEJ4MQe;0|l+THqF9+`^FakQ=i26LJ%Hg46QBI(53tKI06%9oH zSX+%=L^>a4XF}leh^AxeCOSliIeage7&mW4UnHFsip~Q@dzrQ-m!AW9`Jukwtmyo( zFh7fe4A8zjt@0g%O6Ynh{mw7Y-_EK`d7}&i_nh-=|doh`HPG~o}DU|z1)N->1(>XvPr=2GVWyb5rxmMH8FVd zVzuI)bhauuZ7jnz3SOq>hxDO!u&vMOXwKEPx^wP%^RS{bK^ez=3S&0v$#P#-aQd1$ z*j7?UbFQ{^Va~n2v8@d{nsc?Si*xSvjcr|`qd8YwKTMVbr9NL4fXG|h+FSshDgf^< z0KcLDd`kiN7Ye}B1>nLv9+PHUSB`-ZU`vlaC+%j{Uae~GqxIx(3jVa3GjJSaSOC3Y z`zA}|J;xv&Z0jpJnsc?St8(u34W7Hx(VVMoeLd%1-`LhSbTsE`Ti4{=>l@p;PDgXD zcwR2&UfqpreBW7E-8h6>KZT%#BqhdQm{~v3A_6Yb`JM*6kU%41Qz55h?wGWYzhZO$t0r=wz zz9<04LzoDi){+4HSp{Dcfd5Uw@pM^E_`8A!J(Sl4ylad_IjHc@&Y@yij<8={*3|*{ zL;>%#@Mum>n56LU%YjtH4uBTT+vgHpa=`!lrMR6CD6iD6q~v3&7u303Ni$nohU?epvzdqyq5C1>j@# zhlsYnOPZU_w#4puE>PefIR-|6Z5>qrK2|>^{#gB#@Ui+S;YSysUr29{DS-cx0`OxC zz#|3VQwqSR7Jz@W0KB3A{I~+};|st~C;+c40Iws`CND%;N490YBA; zPgAkOVM_A#(y4$?G5k{9614^J&nN(I27IbDw%mUc@a$-+`T0Tt_zwhJ&ra1@2Gn7= z0Q_kIulMUK&MDyTO#z=f27WY5=Tu{TM&s8C_>9=Ui3*zDdAcA76VU*8@JK=tn*r z%^||=fajC{jRoLC1?cYue6q1_rY+ZanEt8O0>9iDDt2Z8_}2j^oruDlrLIo`o{!H; z3?cd8p9OrXvF`LeMdzCZ@NX#q|3d-ziv{5S0(`1iF_i_1GZlzdVo1veUo7CwiaWJ8 zjJq2E&sVRT3c&XjfKS7anUBr_z^5APVB-7*${i5!b9{cdj>GUr0soA=!C!Hf0@qC# zdh^l00`RHUG9O=`gE$JvQ?2a!*q;=}p9Q?d#~0@sa5s#hIv@Re0H12L`S{{|0`5M6 zAv_;`1K?A|&Wp@)gUb1X0{AZ!fKP-gFx6V=m)oRbXBU8f8*u7#tHRTAdk*k?d>(ZK z>96(aYd$XnT=XyH3UZ&H;X8omqjP@&_&B&G`S6bgd`i*xB~kp<_Te0RCzLIw!!TW4Zfb7a4d~fZ;QM=c9jF0r-~-z<*i*ehey}kA6!5__qqc ze_8-O8LsCPV_it=!*sy&mHR1y|8-e;`~_JN)&f4&SYPGY1%?*@7jlC=W8j$thDPXH zKKcu&hebb>NcgMub~)grQ;&Yk!1DwQ_ZOh^cmeopg1#O-vhYg+DTt@JoGsaW(*Vx571_YOE*EP#CWlfX{`?KGj%f=Dt6}E&)%;8~hC^ zxDA&*pBz2`_*ARk$LD@M!?)qePqntl8~l~)3zoGGiaOOw`*7}uGt8_aTuWW!$7U!#k#MaAmcN4e-+&8!Mt^0H>tB{N=_Cm+gT63u&zE5(;Q7k^ zRRQ?p1>kQ0KBee8k|_S_esRn+RW9_1f%~}(Ur_J?$dBPvd9i*6_!MJ(TXiSv3BdF5 zxwt?-Dw$3?UsF`GcA!GQzcB_L66Ibq23{}luk+!0X4?XI#D``h$pUb%0Nm2=Hk!V? ztIKNn%%TPU0rgt_a;TbFEnRKhJ?$->U3~LV&)nI|H*Bb>i7j5RsJXhOX12yVQzplz zFYjElV)~4l8q#Y{p3>4H@M~t6FW-(YZ%)?KEMMK$-IDBX>qz#*m&cmh@F&(VBi6KF z`Qio5Gh3#2^mMmPubFW&DUD^UmM^kuOLlh8TKE64_bu>oRn_{FOs9=Jn($Dg@)#gU zDpK=moA8jNY3La{g+N;1DlkdYw29<(lSvw z$|0|;Y3b_e@9wZ_fK-(F4v$D}~bJHxw$}e}$GPzT2R+ZrHU`^*$R;b@ts9!+X zTu~cfmTF}d9s0nnQs>WohT{8=GD=lNXnMS{n*V0<-wgh%<-bbxrm{-i*Qo!q)PMc9 zQr)Y7l`2rB3SU`AyhDQmyz@D?U|`9AFNj?O4?%ytcv4 zWZRaYquH142d%eqh_4tQ<@>0q^+Fp|i7%>biZ80bRoMiWp9C;V^-yo$z~T-z0zGa02WFX>3FHnP zxtTDGj5wcRsEqAE3aCkeXNkmqJ%L~pWbt!a;t8E$b#X)Y;?sZm=q53{^ z6dTc1G2AO1p>8ea26Nx%g_pHlUSRps4QUjr*pEVa?D$Eu5OjG>Lr6Y z7fc`MSTa44Sh`|GqQ7Hcu&)5J zYE-ezv`Ny)jhSIUfb2S?Uhfb`05cN_Z2H<3L$&>$E|mBM6^Z7v79^VIEvQU1H>0;d zr+)$3K=l35WHk4*)In;ZV_=}SFVWrC-o~K^BGZyY8<9wM4)iTgVEffX-xA$YAeeqz zW8a$HWD{)zONY4cgQ8g2yS%G+apJs=L15Z8`)!jm3Ak@wKUnu6zUt$ z)`yCfXyp2lO^14uXjL)b>xjkA&gOy2sze-n<=V!(s;LS6hWW_O-5ns+m6E9RD-!Xp z!K7xBt%EClvKkxOYFG{(i$pt^4Jclyn-hs8Oz^HHF{uW|q-gqk&_n4;>LFyTIV-^Q z$&xm`Q2@xlhK)Bm92JQf35bMTr9<2^N1bv1P}XI0e^AzjtX>X~&_wKHqb7wS=T zH5FA=v#Kj*%^=IlTJ^88s;0WSYDRTaQ+u~fhN_yO-mWDu69!eu1S%Yi1s4sm9L9P; zhMvv8hQ>`zJ?AYzanS=}OjWe?B$6xpJ6QK2GYxhQEm^{jugHe4BK^<%mx=Z=WV1Pm zbCAhYHtJugOS#RpdHESxm}3m|mRaK3x>2hlWl=*UWA9OKnwIU)Mj%r;d`|y43)m?1 zEK-OUGh-(sa96h=M zt3)=?%}00N(ysQL`YY4K%}@hKGfy+|PAC(?<)s_S1LKwL$Y%O56P=XPc_TYqo1v@L zUW`6f{mM+y+1GbzQ#0x}O+3+wJ2y?|nMAGKXQF28l!;oVduFEf_NO`%d8k(@Z+hgx z(X6ZlF(w>)2bW+Ee8#nSMsB9K)Qnf`pH2l`%YDxXnQXAagba%AB2+3{L2 zRawp4FX}*ToH@x~ZLv;NWDAuT105zgM7*+mGhNxd8Et<>_Bk2LAT7z6?dYiX_Ejg& zud1v-tqFn+-nFEiBa zJUS+oiP~mP)GugTdO(vIO}P`Ni~>@4qSZhRJ6x*6sM>}D;4llvOCU}iWD;VLQ1iIw zEP6ktmvWM#vQj6s>Vrc0DrHa1At$jOzz)HLmCq~HV&JR$6PQN#8Ga;|^l*T28Ptq1pBmjCM&ioB_>e*v8`c6z-a@mq=Ps>$p2Xu@=K)*k;l<;Z`^ zg=>K0m~uHQ(c*#9PPw4Ae6`DCnNs~~8M#XpIi1=A>VEIPS3AeASNnPkf~-`k6Lgt<*4Z)LG(XYXfjND>{NB{_dUq!C zaRDmc`H2pEW7gK*fvHpu%$l&&k6|4?b*M>jeJ(SXsV#4VF2V?|au%l7JC+V$F+xp< zw)G}si&Wxdio7%6pJ_h1s804Ja8hCtOI$eW0rT;dGfeW^nNwn~MIT~z_A7_k&Q}h6 zmR~u56p(Vuaz>wO!ZUrts3m_V@VFjGPO;1is;pSBWcEX)n*-ZamTW*_6{yMboF*6U)`5&xjZ>dfe&P{ z5^L8mD{~1b6w{Mkcx^u}apvpfrrEx>_y>K_nPYf&u6?Fm!_sScd@$<-cc(2S>&l8;6Xm?in?b|Z)ty*>Tb#i8 zux2GLWTBoROw*#L!ufCqR1m*vpJ6_2=g(q(itA2Py{(XTb0`UF-{9%Ffw}n>!Ip%kBZoIk# zF5{IBz`qoL?-IC-m)$>^a-SFa*x!K=m+{s6nrQn=T<=qokDo&ZaP)qJ%f|NtflEFg z6Zi{){waaW{PS0V%lzO?MqoI;mhoLAa2emEz@n~Q;2Q(*y94m;0r=wqxZMkv z@sjqOZ+>q`{6$>W&I=6AYN`uor{B531$4>})ssGmnF7>}d;M{}G@(CSGZL~g< z1%8mhDfhj&EPbht-1?h?4P=gR^3!H0k;9Mks@!RG{n)1H^$vi3Py&`Y_Vpy#j5?eEyQ zPXHbH%XoDOoQJDh{&tU6(o6lX6ZA5_-5}()B8=tpO+hc~hdT`Z4$w{&avv7-QtlIi zf1BX{oWK_ee2?H$Ecj186hv^0m(>4IgVWBfxUBvs2zsgiI}GmX9~1Oa|BD4KZCm*sS}z-2l8uE3?- z9|&CL=UoElk!u;b=0}#a=Me&*V{q!(fy>gz3{E{C68Mn7#{_=lVR(Qez0~t$gOk53 zFO7m;mY1^yF3U@&z$O31rU1I_-AaQ~p9T@%{}K34@Xp5f*8=~uz$4gSfsT9*7knB7 zF70q>0Gk9&Zv(U5FYtu1)sMFT&Z9Z_v>_S zy1!T8yYSxX(=TwT|IGqFMx@sd1TN{H6u8XKFBqKZ_W%a*G@b3YCGt3X&An2w2zb^19!ROlouNL@`M}sLG z<5eT@69g{pFhk&fL>Q}Qo4{qiVx7ThAK8w7M$k+BZx*<0$L|%m)TjCwFotvM?fC|0 zykt9mgTQ5b^mBp#8DVU^4#K`BbmUVh>{BmrnV%O4{0|6Y`79Us_XYky6c2FZFY)T* z6r5KNBm(d&1MtlP-wu{mpI-@F%6%>XpL~2)A4z|Nz;pTu0{^*?d!xW*KjBt^%XHZy za4GlE0Q|WCe9{S7{bfJl6oE_r=LFzi5cp0Jucrd=$lJ4WCI1rw@OK#8&Cjz0z3hi< z5xA@me{AqVi0{Q^ice@v(93f7oWb3C`}mWx`pbNNhQMXKVgi@_kZ%b5jRo0!c#ptm z3jEVl9F88dq)FD$=N;VUXYQv?XXy{pkD=Qg+%#t)?z2wEawqL@`4k0b_?)UALstdh zA93)6q5q_Vf5qT8I`~}%|FVO#uBZE^gYQsiDD)i%XWK@1kAt(!)2%l1VAejIc>{O1lnYVhAUxRrb2;koge%z_Vhii7L@+i_l)gU1d1sSf^5gI{OHU)FBhBbxtz z2jE|E@MjI5Z#eiu!{>GfUuN*{IQT|`f8W8MH29Al{5gX^_$E#L`vwR9xWTuZhS=tl9R~lUgTHL>#~r-F)Q`Izyw>1uOE7`*nRT>QiAc)-mLz|VH@e#585!Jjnvg$`b1_+RYcc3o$YgKsi?mOA*`4WDiY z=l;obNe4gO;KL67C4;Yb@a+aS)k^3c;CUA=+uw-do{q&<_J5`+RO5H!-qQcO^C=IA zSEazE9VQyze>MK5n$Jt$vVi!`3cx4IXJ!DOIRcmCmx=O^3O;gNmJ;~GxVQQCqXK_K z;5P~U7Xm*~;4(in2H<8IOxgL@g3oD!{x<@@NZ`K}cuxSn#^9{?`f%BJtur{w?+$@~ zOyE-g&j?(`*Hja#eqf*4@@HE>=hhF^8qUL;0`TSl{C`z>(al8hV`=8^uTBj|Je>rr+W6nKC8IN6I~@D2q$| zza(%e*N(qj{!26)rRS5lx9Rw6VQ0zz&jC2+X6eZPDO{G%RRrN&xz}r0jYs-%Z|P-u zd|KdUnK7@ut}=YcU(RF95x8ux;{uoE-3;TDo-)36UV;22e!So#arP(ZNdE&|HeOQC z-wWK%Ly%tT(|~6>@_PxF<>L{ABYrO~i=Rgjj`&}2S=`QBkpEu=z7+R##2>(A=~obh zBmOsB7QdDt9Pt-$S^N_O;fTM?%d7{nPen&u+UGunYWx-4TlyahT-srK0KP-ulE0lV zapiIjjn2jYu3?2sxg`Pl{A~i4`rCOCm;Wq5|2xRC@wM|bq?dBfGW1NxQ9(an;Clsb z=WobIrtd|9Uh-)dxa4E^xpw({z|gz#`iQ_~y4(2@m)_3D5SRJi&c_g!cGxWV%X}!? zuP|t=J^v)=WqvrM5RBnmeU1shD+2I$3taZoVgi?P7aH76$BP6m?Y~&?k?A-jaLNC= z0Nl=(y7B#AK`-;|4+SpyKM{Zz2DF<;7@YCljLW9WTLsQCWpO)SMLlJFrwRH|!KXst z(r$L%$mL__pIkd!Z1@nD>scKFm-%p&z@>dY8i4<|z@?q-e3h%u_X6mDDsUg7 z{)51!o$Y*^YoEUd@VE14q?dL$iU6Fef0@7~y`4YfYgs?oc{t(^@||ff<_JDAUF`fE zUrYY_oI|aTtRI#NK62e;z~Ic*|0(3I7W9t_e7(TgrdT~cY;f9Nt}lO0(986?UC?h9 ze7+}enO>U&A6YIQ6}aU8n80Oz+beL{zQ0xUKP8`fgLB-&w#j}cJYUeu^(i}lNii~A zmI`{=|M|GUrQFc~{1*my>(3nmm-T?12c3H~LJirl``kX9qY0pyy zF7w-&0+;R49Dz&vn80PZI6nYyH@I8g-!E{Pf9$-eYlq7P|EQ>k?R+ZfWjfmVRM$SA z6MUq7>^!T>XS1M}c78zMzk+O=E{_OY>S^a|UH(rQde=UG7Pz#Jo!=!NnNQvz##u7G zju5!4PmVV@>yrVY|2qV|tWRbNT-GON7~HK-E)n$7ZXJStvyj^@a2a1aZ|%m{u7?tr z<-*Qq6PI%TTj(Rx{T6}C{QP5qOFoYXT=IEQ;F5ld7|+UddAq=+Tsx0W{bhc(^XSCo z_b~GWA1T+)qmy3d&rU%v+bMlsYkqz59YHVoKPYe+ub&HC#_MH)%X;;#tgzvzr>y7Y z?;xc9=Lvc#_X2^-dMqJusm~IF)06GRprDuS#VUczbXg;Csn0rrOSvC6xZ5s#M&Pnt zu^lkB-sw7imxFv&Y=iO9cZMvYB<;$iK={MuDa_#R-iOY6vj^HE9b&J4dyxwbYsy!(9cL-d@t6%Vu@sjiE zlK*AW4O2J`&NN-Zq4Fy{qZdVKVHyFeQE;mGXwBT1b(#OzeeDa ze^lV|yX#X0F6kEu{7Kx~c#R1Bcp>*20r;_?p<}v8`ZWTV`dlk;sgG%96d#F~DejuT z#LpMFtWVkk@Fha7%(p!TXSR^_$qIqHI&~FBRYyaB> zF6)!;2|ltu`JupNydE?-b(i(YqXL)l`lH|@8v|G2xd%YN+71upsQ5cr9> zxAEF3aH;3h0+(`M5V(~4iom5kWj!qI)+p>I*VnEUxU9FoC2(19KO}Ie{~rQy%A#}A zMXtY`gt%Dyyhp@K+VebtOSu;aT-q%maA~)t0+)8XS>Rj)vijUDa4Gkv0r;)}{4W7` z(7J|fm!;hQ5ameL=UoB#QDS~r)*IypXE2tjBbZ2V^@9F6JX<@=H8|_-y97QU=w-fI zDd^?=>J5Tk#_Mx}ezV~JWr53j{+oi2tmiigT*mi@24{Si3;w?pIN#ZN@^QgO*27O3 z+-<)OWd?_H^ZAhmC%uf<+XOD-b%Md^$#{7Jmv(CwxU6q46u8Xi?-#fnFRv50|KPk)6BZ6M?m;C|BU-kzi|J{O* zEJw1RDe2z~o6ym2ry#6NmuU*s_{joq5coR;eu2Pcx?f^&H{H7gF4LWTT{`L`(|wgf z^Yin^4DP1;rv<%CcUvCa{CtbxBh!7Wz-7Adw`_EjE7Rp+g=$>d`67YKah&|VQI6w& zCg|TO?7UmxCkXsaxTm8&(*Er0($U7Up0V>Sq?h%KoNu`c?`(c(75rsAWBWyJd6eT* zX}7BlAJWTo`H;Y6dHlGLE7RpBfy?-Q#o#u-O@$n~dj-9W?+*-4ePn!PzgouEj>}v- zPo_XPE7#iXFoE;d_7jdWIQhtYeuBYWd!8Zar9I~edTGyj0+;q|5qzXQ`vfleFBiDX z=T`_^=JSsUT-I+l30(5OMc|VEZ3374w+LL8mj?tc>23Pc&eM_BHeJf_Oh;VipOY1u zpN=yHPEj@;8w^f9G95jGyXm+{&`Uem`p_*Gc7C3?Oh@^ho=nH<0{DMc;L^@t61cRR zfkP}Fb@*q$zckAc3a{l$jlnt)-4ctu?6AVellr!MRCz>6!bQ45dX5k4NCoJaJM^n$k^*Hfs?=GRb&bqgHxY(Wga)HZuT`zFTEiv|&`wx(hZ3}M|^rw)qadMvl(w}ei-!15s zo~E+-i@-^5>xD*BIg(cDxme)T^B|Ltdjw8C+l<`H1x{Y|rt(-PaMC|-=(h=+{GT)Y ze<5(v+y3!O0I;W_t-y?9+Pcr%QM*=@X&_5(_(w}bVk2H-U#o0Q8!HyO<=`S_(7YY1K5*pVbaMFLk z(0^Xw^_Cd^*91=bdknqYw}CqCHjU5Y4*esB{xnl=Nc;bAS=TCX=G#_Nk8}x~a_#rl zD+EqnC8pxOQsAVw^SloWT*`e};FSBADbEqpX&`U%S!2r4kpfq8P2+a7z)63dp&t~u zlzY9vDK}}#;f(?(pQj9;TLn(J8&1MMy6J-}dy;L7;NuiT<#*aUoNC@SohMO-rY~+w zwuPoI8XOEMU^-70%xtr-H*2q1-r1JKzVy>G!ARv0se{u3D4YXm_em;FwcHn-)@)8i zuv)IreUVC``=ZO=-hN*?R*ExHHQmy7oa(%yy`w*ggQFL9cjVYHpFGFj_Hd;GIXbju zMxx0_)9kV1GqLx6vac=H-PQh{E*wgMle?SGT99a-x1ci7+>G<<=kzbY3EenH6s($i zTIv#^>3)%UbsU}e+Os*bS*?Z#)~FLRcwD%NQ6IA~EZLIm?~b+OSeeT9&b9#_rQDSq zjJ1;-$2|v|O`TARcs3z=h#ig&u1&DaR8FtV&GN6M0AKUHwgMb$R?bYc^y3_#Asp$m zSRJe#i_1gRs}gaXc97`rLmpV!RE5*nd3F$(bhHh25#8K3h`DH~V zTs7=m3&!o>JK(a#>8uHTnrGLNm{blY{r2_^^xz<(%=tmhdD$X+JYh|O2awdL14z!s zN!l|KiN5|pSRj_{7)W&d=TKYsU;<}x#O7%tobTSmKV*BH%sSJY2GZ8v-hs0ytVDIz zd!~{EU3E_fjx_G=?MNh-ci{|IaPGx1+8wc;cu#dzQ@m$pRSo`E*UjSpO8l#uF>_W; zZS~B`8F;89Qd3n|Q#W%~RUHx1Rn4rPSyxp%vjzt-^(eZUimIwv)fKa5kY#19`d3+1 zQ(av(qq?c7od=5K*1J%x2B&P{IMg1Tp^;21hH?X4i-wY2eZ8?BguvNL{~F3S;iT3D zC~-`q$_gB;+}5Lx{%FdL5i;9g=g^WR-N<0s8iuE7A84jH?_b6O%aHfxB+fz3%Z)SY ztNqn~{%7~GW*o=aeN_pS_4Pqs(+6xN8eB~V)~uCPEY5ws$Yew9$yhTgTSjUYi&6zL z;qtbD#W{sYSFW{r@tx>#-V+w=NOXEIII;2o59ao53ST?!*@_$|(sW(g(Y?|{#BG6{ z_=!5Y3bk`rJKFexuHL1=jihVQc_uAu1Je>Bb=Ju=nBtL1oXJ>Odx)dq|5@jY`?lxV z1|;Wn$O9+>P5VC_GXKc)+)DfP!*49 zjCr!n>T7fWCA!tgnob<29>c+!xkvu-SswWZQb791KOV8G)#Esv1Hso*45mRNtd2=` zGqddr{of~=f3NF6PE2;=_-fBQ=Hbb?JpJ3;f7J)T(AV46-IZLK zSXL35-rm>K)6t6s-QK=r$Mm^pFFa*1i6id~r;g6V5=^dlVA*M)Z>V1{5Wl+rZ^HVv z{Ep3Xt2Z}h**bI^p7|Z6S+36bLE$|U-hT7O@GB<}{vx07mrWqNHI9_e-%^ZM|FuTG z`OHDZ|MCfh-(bR*7@zT9J%R8WO*kq4za|j=t0ugZ|Ih@&-{~X&iV1|j-$(wV69{j= zACvL_-2}ovX5`E8zn?((-9F*}FoEzdoA9#yJU)T&lL|Bgss9rb2yefgmf`<6f$;Xb zTN!@W1j6&T>2xyu7@o&l{%TBknSa+zAiUitMat*zU&brH*+>7wfQ>i&MLyyAyYTUb z@AL`J-z1JVy#20O#{bF*#D9&C{0~eZy!|dz=KtXdgul*5K7aQ$-uT<^G-dqPPC))w zedP0Zvg4J1hfnyECJ^54ZzJ{R?{UW~-+o^z!}E8G;|>3)kN*5U^7;Fu@rG~qkd|H~!c7gfE>y`0IVb@0o!9_V+H*en(C~{;fXp z`F*Y%|0po94Vll8ai`s=3I9X;9j?J`f5g)9w=(2eZvo@4`GLOKZN497|DWHx+wxp$ z=-v*W^3CVd1`UNi_>RnN&JAg$dnX>q^FYJ%+kX{*6JN`FgOP83-eTd*?+Yz(K|uJ8 zCj2Ru(D<(X&I7LO=g9BhsRMW>9PM{1F4uk>Q~K$@ZJhFx2=6EV4j=ia`^f)%fc((O zcmcP+_E)&jKS?et&@cdR7c@EML;O@_GJ+U;5wb6MyRB%Kxa5U+ARY zwMIU9OGjHtw>3cjjV8SOtr5ey`g4B+KmAL_w3G@XjQMvSE?57zLT~D1_c1W!p&ds4 zmn>j>S3dWj^OIk1%RvLm!JM6(>2|V+ROZR^>0FWKl^Vp z`j48Bw0{dOSO3o%`L6xHY2@$E{t*cG(|^>2=bWQ-uKrZsPyY>1=}@+xK>g3hZ<+*Qu_6ybPAU9@0>KoU7pF?;* z{YQ=dttLG6UyRGu|1C%#Kl!)T>wwML%lvocABpgO@}s+TkX93t@|WOpSz{Us6Z*BJeGm-w==ATl8_tXE5L$st( zjF0H3|9V`m{?8csuKxFQFjVf|Lwt7i-}>eN{Z06nZ2$&_bM?O$;r;Yq!Nm?bp??~W zuKu4u1y4%1{@$^af%O?k`cD6MR)TZyZJ|T))CUCvt{DyUybm7`KQxI|IdJl4R85&1;{V$ z(Gtt8VB_2H7G4}6|5hWv)etiOeioMvZ{>d^Kz{u=h|J-`Wd7m2;oR0_1Nq@~xd||1aRO;VoW%w9db7{=0sh@=ri` zzx=n;M?UAWUHR<+@;8oC{v`qOLr3ZQ)5eqW|FVz#j|IrTd7Sb$1jsM-kPnveWvjQk3`lkN^9{}c)0URyQm?=j&Wve4ruyo=lL7C!{*)5M(S zGqlr$zu%bvxAwE)ry#t_e-<#lXX5_|=11t5{@=u9!`pn}C!6%QUe5oaPzCtY-Hz+K zxZLn$Z@M_R%eozGEZ{zE=Hc~I)^7G0!x!K1QGVeQv&!uY((N4A@KEXM(5~-ta4Xi?6%a8-8hTEOlS; zb)!Z17O$&E`H9s%UHpMryh$%8D~hFS%Dj<_%9_2A#u6{}Td(dRFEs~`#b-U_6+EX| zr3s`am6@m0%f5^0xKO%iD>Z242L1QBe_j;)Zm5Sc9UEc701<jFJbn;^}T` zkt{23*oxH(lw(FRo_aPD@zj0s)4I#bi`QNTW)0~#4gVDp`5lPvz_sQ(JSyEQ*y+{X zUwrld1F~*=@mjv|iJq5ELQ&Yi)|%buV=S!fmMm+Ija*Tb>MrXi>4mY>l-S7J(pYLP zqODT~34?dE-teZ9hSc10ukN?Tf*#%#@m+;CJX+#qEbCplc|DYhr(SyB#SIrVT-ZUa8yHT@~)o0@7-H`Y<&m`$Hy}mt&uYjM(4P@%NZwT8KrDI^?Q@;kb98R zbPQ6zf#LLRI>MkCEvZc`)f9rJ#cTOSQSDMRsRs?Kc-XOPr=vDCvx z2yP$R=j~QOU>r>ZKZC|Cu@qoAip0 zTw1@Su|DLb9;e-Opm^$1l$+mpsb{^djpfP^WV5xAl@&OSy8(EErfqLwJVGNaiA#dIGdLV*)yq0BJ-296_7Ut8bvtZI>H{-yIs7tlN0qII{zosX*taUh&fC$eA9* zJ$Ut#Y&MP!KV49~?g4OBX{EXZ>h4wa1}yhrZKs$XijTU;th<(?%*t2AYflBD)Y?^% zd54Ztwv%LyVe`7qd8tjt*X3RFWH{u-Z2u}XWjFmE*GgMZ{ z-;s}le&%@{i@OFo+PT@N`So{DsDDFzZe!!A(Q^E{dRnx0diC_mXjMgJUB!%wSJN2WVo<( zY7w4Gad9k5=l8Btzw&*jdxU1se8saf!*invw~)SiSLpF2<=tuCsVd3|_sBlpJ#XG| z%x!qqlc$Yu!DY>HWsNB}km7CDCiQqIBLa6Lu1UO^J+{56Ghrj}k6o-;K%jPMnO4F^q) zHcTyTP@$R*R`V90CM{euE+rRluPOx9aCqL-XwN~4yg>`0j7KIYgM8KG1-wsT(#gi{ z!-eNgE&Wh<_SEReq}fx;uZf&Bwc@JDbEeh~PnkP)v><#&;XzYtp>)I4a?oVrdKTl# z`4&2Vdm{Akl&dCR6B(KGq40+>L|1u{^DlHtUOqvDg(K;NtYSJ~~kwWGvt21R<`M3>qRBKY`Pka3c!g(t! znkt-gI!!iYIOMMASbCc?v$VVmhmOt#%u_hT*|B^VRx@ONIA_-KU0BVY`Qe;(%XeWl z^X7-2oDTvC>)IgiU0BV~<-wq5&dzt?oKrrs@56at9_V-Bye|(d&RM^F7Y=b2FyDnk zb@?EWu=+Jm{;P24w0uy}tBI1FcZcAqP|(}8_LT;=&&;RRg+rX7%y;2XeLe^z9GV?~ zb2c;Ih4a36(lp@^XFBs;IOOGnK*FI|06s4OS2cCsyKsmzrTH!#;;d=D3#+f>{czQ~ zH#i;4c)CpC5i>aMo|q{Xsp{sk%+#I}PsEJx?3lEqTu={8Y(8-S08< zQRmIe>RGkWIdAk-ePs^D2mPs+t3B2ZRDFzw!f~f6+|@o5(r2^VoZ{5O?9cFOu}juCt8Tu)kAyw%xQP;U(ZD zd0cS*9ep5JWx1g#1MWwEasYlN@Z&{$ZRMV?@N9c+@xcImgTjx?Xlm)d7J&ay z;U{G1RbLrTzf<^$8Qj)@Jlp9wQGeR_z8(1SYPC%Vv3wd8u7(oog~eMHo^AK%8%e_f z_!kwPUC_MI(7youxI(L4wx&1?Rg9ngO9OD8>E}oPnE?EI0r<<%$B)ksL4UmHZ&>{+ zQRVs34+Y?t2jHIse!S?nG#mRo9)MS3HR^cLZ?XD(68PbwU&_8d-QR)x=~<7e%@02} z0Do@)-WGsg5rFedyyMib3blSVAKo57|8M}#GwzNn>2Dx?w!dd<-t!b*Z$f9k@#+Nb z7cVwZe)vZepV@hQzNYY|JeM6!4x!g$BBNRr4MC4Itb+) z^i`QRZG)pAa30HGM?y`F9^lCs*p;q(@$~8n3d>F@<;gnWlQ#^1oYNF6=;0z@S7T**Va+JOQ5Q^ zeW0T)+0k@fPfw_)qo=)prHWo9POmc2>syNRcvRi0Vkfek&dvv1CsOB3U2Aw%zKW`5 z;u*s=^Egfsg3j4fC-ckXYtk6eFgRsrqNhyNEJ3zYITTrxDc0MU;C`%J;J{kofLLgL;ur@-msImD*vP>-L zNrLTV4oR0@*G#kxEJaD_B}vHE0ocJ<*6I{2wlvON97-r{Rdz+hsrjM~9B-FE)Z04^ zvSgqmCmxCn>;INWWX1eceKlEJwp_VuR5JVf$b1z=LGJIIZ7rn%1$zG3IG_OLSkbnq zYgr}p#9$W=J#52)g2|Qr9XOb8NgoqeGIi|lyvw$LDqK#wIqzDsH9)=z@MB>wgVSdvX@W+TmeAFZ0h+f<7VW52qq<^agQRK5GOn z)8$Tq_X_$kkzO1#T0SoX;O{m1@HOcV$3@rS;C9`y-@!)}HPYI_cN+XE2e&H{A98TJ z=kdoK{LQ9(zG&iW^;~T5zdJbB=IIO@^_{y+m#BKH?JVsn>qE|}Tf6s`TryA854Gs_I!)L zm*C#!XS>2m`!5yvMS{Ll;5`BO8iTVuR^zh#*BPAry9E9*fy?~!8G*lF&|fNW>R|a_ zC2(2qU2kye!@A1SOa6TVw{v=KzGaCkg!h z0_T3&bmSxP8i9WR?<}9w1up5&6S&mpVu4>N_zVbK(yui5J3zZu(61NtTt%?@*ml^} z|9U|$%h5)GOZ}U{laA%u<{3V(;6FGQ*Qowfi+RbG^CI(tVr(5k$i63@BH%dUL3})=%)CuU{0-_(|NFeT zCw`E?M+JVcz#kR(Ap(C%;D-v_)`#TZA#hv25ib@v_m8I|ZqtI#D+F%aIO6sU2htxw zz_@P-`Xem}|51UzQQ#$H07uWQx4;9zLFJ^VO3`>W^B?1Gj)qRUb_^V&YgJ7*~5T-v_rvU6wqqSI=rRh|Hy@@Lxi zNi8OM_f04C;{w$9rE|F{d&b>sxu)cMLgi~I0OypoV|hePkSCClx;t?C-fJshz#fTr z083x)3depIlC;6FFr6s{`o!;vE=EjPup=>3Jal$G@ELf&^vO;-ziRHUv6;rP0%Z#d zs;kQB_@KaK5A~RnZbQA@U6*!ruQZl*^Jykz?$o}vNWe*mVk$pNXR~MU0)fs9N!9d8()?-hJ`3p&>dIj1mco8?gN*B+93|9a;h z-RQ2rIm+h}`z_f2^Urxi^B%%)+;TP+b4Vb5V&@G~b6z8|!7GW;w&Te;Q`(toWAog2P(0^zSW;ccIk`Y)b9_?u05+h=C@ z?g@my-GrC&>n0F>vk5Qd4@@BZHWOavAC9lB-K`&F|3`e}b6je}SwATJV?OfPm$l)n z9~Ay6ANd@&+Hlqn3jdOie2(XBIO_+6R|7%NOZ#6lf$(Ol&Ybegc>pWd`a$xKG~*sA zpW|K|&iX;&qdws|k8Z=V>oQ`yGwTwcko$e)31hDL;wuD*YV!H~Yvx)<^!0 z0rGc`Q~sv|Fm!^Rs`e zkP^c2~7DX;&SD4OIAPmqvMP}>uEpvgG z?9cTDKl$r@{diwW<>ztw~{%|IrIm2dg*_f5QaCcL{>aH%QyHl8-Tm2aPq zM|fpF#{d@^{XIiS{p)er@Rsj;0`#v4knie07@)rw5Z=l+YK3|O^xx>Cf1{87pAOK! zH9)?r|9t`auLub5>OUHw|4twM&+^g#nE?Ga82QIoLmA)I|7a9E=07+8+&WJEOA+2L z|5Vg!finNZ0NME4{Bv%Ae7nbRxzXEtHoSd~2gqMxv{sWnB4R3XorF7CHAP|8oKIYdP5i*Q);O_VXOvyYe3jkY7Je`P%~IU+*LT zTp#&wKtI!+;# z_IZ_!zXgn6ZsOl>7}Ng$fy<3Q^>JmB#WvIMb((gV^TFrga>KKmi7r^4tBq=Cn(0Sb z0yqAL;e}uL>rdAqK7#m3=Z1e*?Tn~&nZ3RN`|b5TtGcQN8-T-Q_WI6<58CVd!aZ3H z>eX$1=!VdzGW{;<#O@xuJRGm_nRDfALU_-rh{fUExX#2yjLCDFxo1?_H>tE z<7&h;$2>Q|Kg-?vAO5GTdTQmgTKh!x5Hm-?mOTwG54MDyUhLj;a_U*yW#hk`(F5c=Dr{P zf13LN_=Dy?34h4kFE{=Q_$$r*WyW7+{LA65Husz>W<0LIwZ`0EY5WhsA2#=EjlT~5 zRp$O``0LGm3jPPpeH#8X=KjOR-vIw3=KfmvAH`*zXE#?pwgr1lE^TJgX}r$s03MNZqK_i)CZjD^@YdD-vdBa@~ytf^f+R6O=` z6Ml8_5HEGVH{v1up9Y@BzvTD4x^2mC@7W$tZ5^pO$l+M|;@Ay$(6E);$8Oocom8{> zHe8@LZ^w46FL=XSqI%Qftx?9~&&7Rb?I=EM?hY?~)^6+?y*qu@OV~SFaZfkZdm}@h zH?oRbIBS6;L!Ef;)bH1=hd?iV+pY9QzPAxxYM5B@x7RU2)(`W)mtM!z@KVFdORu|L zKhZNxhi@jdb(oCkKzoPwM(L$LrX*|~z8_C?+$FX05Tdo4@Yqp~hXG#vLuJveE8_ z;(M^k`S42x$pyv7uE8tp7L7FTZQusF;5l?#47;OZWBt9&B_JqXTZ%Lmat|TRhch&#W5dWl zUiy36ASpg_6N_5B?vG|SP^9(9ZoGgM^!sn|eeq#wg7LcD#cQ|oZE9G(O@HkX!s##U zAedf9?c=Fi6qve6fvI7Fj6^keFD{933-9kv=_=mpUAc)A@${~1=!)cV7phj zt#W&8&kM2CQ4hyc-=ZLI>$;cV(czZtnT>I)e0{qE9vx7{j<;%SUa@(6J$A#NQ+9|k z-XXhwj~(PqIZG>E@zhO9UTT<}cfAe2>eKHV7Gm%IiY3L*j>bp6UW9jgGhbayP}Ea% zqae5tKFY$}74T68uD+Q{q^`b0d!zJ5t|n)1t2)om+d8)hARgzI(BC?@)I65c-!iuX zQ2iJ{P&8}kLf-f5`xT+BSFh(S+?K0Zknr;A4fNq~d%eB`8C9VJc&onS?fv#Pb@ev= z@)3C2xL5DMEgk4~>O1h+t?t&G>7`FG?_M(RKxUJp_^_*KBY4b%C*)qCqZ^%)ML(EB;FBCgw$ zY%u8-ANdkhLp6sIgUzy4%)IQT+1~IOlp0EwST#YXiJ)3$2W{;4yh(}Qp!L&RT#M5O z*4&}k3?1%mUBf#XV2i%sdt z?+e)sIK@ltvBc1myMTKm3nHQSU99!=v5(rC8#{3iKUJt!vDD+<$Qv0e)M_Q($N)AXe>QUz2kdNldFj*3V#D{9#Ic3{EsEXP z=d-rVw4a&wh=#q;8@?=BSbWttm~iRY5dM(IGdVu167p|ex)x^#6tDeQp=m5|x&t;3 zFZa?b(D>rTiP;yMV|DixUwxNiM2b%AOa656+6STd@aj-u@w%-{7zU<*@A6@Ib+iDo z6U|}1a4GU?7_g9R&a6Xjc|NPBHT7@kwmSQCeUsVIU7e1B($a^cMT!q= zjE;?(12CYEk*?B*ilwmq`+(LjtDN1rKUs#V4BPi3Hsx{;f5xQI9GGHhF9$6mWXbBD zb&tx((_seeh@Af{itWx>uV5o=nKdvWL+cB!u@p{|m>}C7_`$?KfNkm@!?x;x+1g^W zKi}4n*54mJYl@um=MA5VlF{pwJ}$qpV1|m$1x2ycE4D>TU4Zj9EAt3nK&1#YT8+4KkK47B(;Qe4*7U+nb{+aFPIf zXw3IbPQGWE+-$Oa6T45o`5tPRlBaUL)?jDDU-#}+&7Rp|T@~=nh}lG#y!;o3i77h! zRV*m+RU;Af(&E|i0>=ra?J4y}(4{lBR7WsmPM~0tu?o)CSjZHpv9hhF9WU{$8n)Me zMs@FbY1KzT6`Z`-8~Mb|s>b;FSTXypICvplk4k$qS?ry5VfnIW@K}n+p+8{|m7MO? zT@qc6YHLe{cjZ=gX|!If%sK31RzYP|VcITTJEb01M6uK(-;F9!&zd6$P+)kv&!GSf zADku>#UUG+R?_rbc#4qeBTSnB5u3v)Wd zrc0=@h11VFKc0FqYoZFI8M-LdY)_byhq7CE%I@L%q5>~njmAeGfkKp>L7jxzmJWx2 zgh)H~P(W93!;%eZ9Gm*N(gh73I)R3Z>KCQ5M|bG_sOUBEp8XQ5`Y|v5FY6+aKAO43 zTlcG+e(t*ve|vU=m#)Z|0%VyOzAv8IVNSZTeOy#nDt-PL@iAx7=uY#MPi;gj(sN6_ z^m5eU7nHMGQ-Zz%x^}1|ZMBH=k(61}I07g==;0lbwxt<#I4=w{1G@85uT7sG?8M7{ zyl4esGrL8tne)Mh*VAg?jjm#9ZP^+;6@Obz4Xj^V#)%#;t)~X?Qjw;IIkxfexBSBR&YW1nO0iy0afo3kKhgH2z!3RtM18R^g-gp?s+0SZ)6fq)>7wg)p_Z|v(fQ} z*D6~V?0O6XFg<>c8zH#?hhWUl8-=yv@l=PJ%$w~_<{`0kg^V^rWrCdPJZ}$bSSBco zQoLX{VhkfGqZIAxM)ic~@A{ZJ&rz3eI}I~`A+xo-SC(bL$DhsX~UlyMm>74S@N2o-{V{wQYP__gL}TpW!B+ zQno>pGWNDi{ie$8g88ZERnEN`WRAgAjAQLEAtp=N0rv`i;8otk1p!k`J(SVZUv1H! z&j||D%wKf)%unC-2)G+|I8+qnyx>Kza(8Uco|v8lCZqI`+r}oV{`b9Jx-ee1V+iwS zzeAS>Dyp#5bKQkV$v9_uz4WJbwm}OyXa2|+7~!#lReSh0wwe{*$faoNF(NY)aM0{A zHDVLa7C?t3S=MhJVRqyl=Cdd(13u8PK(U@@ zx-hUxQ9h5Hi?(TP8STNW3J6a-NHKyFEOAf1q*k4bx^2a4`O{(f;%3A4B}2H8&sIJk z=(eEv@sW_`#;UuiAsI+-rLS2<~U5jYssj^ z?q%`14%XVH|5>{00@cN=4`uqNyXt{tuo;-xP^CweS-LM-(%$7$ZkAbKgbBWZ{0n{Zm@&||dd zCNqDZneT!KH4YrPA{rlQufTbn7vdOeRtYfK)Z)X=*@0D1bQ!P;T8KqZ%-kZQ44_pw zk8`~$M`B($p5kaot%;)3P~oK)X1YBn&nxh%(@QmW;$1V|U6>8oj}^I_Axy6{U0NC+ zxosmJ?UHFz(yo^_p;^%*qV&2opxCO`M{$GFp@&8zY7NUQ)unOhY<3Mx6R6?Z{c|QZMyUMwjhcuo@zy!aeh5>%&=bY#mUv^u(FSge^I_I2YILx(-&GlHIg?O2{pt9yu@HGr|P*J zhh2{}<&`b zns zYd;0Zj*l^QQGsAuep;2}c=~%Ru<`UwM7{Jaqx4c(UrDRgd}er9TvY(^kxo?#O6J!+ zyQ~NOw)q(C#73^VpQ^BQ#M1xO8XGwu+5TZOBmq(n1i4-yr9hDBv&g%%Sw42@?2D4;t9m@W8vT^qVz#;-NKPrkncpK#kY~TC zz+erQ%a*bGDA20|*P*Y+>&nWK32QHYmVgMMOP>u*3{g@;YRi!rBE^a$BPGTO-i(yk zm78_6%F2em?4`eVD@Ee%X)rDA55qsgXpgiO#p<3~cB~$MDNSJlm0-Y^V_sVYH7U3X zvCr+_tU~N_`!=gEu2=n<;w3A+r;c-eR!vqtmOL|)cXe+fE-6ZPoqfGQ|Ekg9!$OoeT3g857sAjD>?6TDme2s6xpI1P>U|WMwND4vqjU_ zSb`^%gvWLHL8)iqNjFB}m8d+(LgJ+x%P~|cUi*wKHb81gigMnFzQv`j=s%zdYOK)g z(Bh;Uy)BKkA<%FN6+QnsoCauYj@RuhUi*OIL!_0j(0gdS5PgI-2p>;f+6-0~T2|4BClqshH`4pbB^4%PBXFGp`q8z~DG=BWC`8Rh9!&GHcARpqg|mou{? zt1#`6Dfa32VU^)m$+y{0R#25jZT8bMwa225O|@-t*|Z_kkLAZxzq9>ZaD6(tI1^UQ zvu?I8OUJ&GEnny?Hr5s&hLqlQ7&_(~@chv=kWf4ga}>KqGT$8mpVwMEEshRxr3Ov@ z&dk%^pz^mqB;GE%*&%RhSLOD~7h|c>Ocxi^k6!8sFO8i5-URjs?&69sp z*AI4OhN0lhS?+r7pS8r;iMqZr=VhaZa*RzBwZdi3(Z<(-zoJ09#G~1P9~NHHeMqLi zF^|2S(~Ky;rSWu!>x3aQ+H<_0_(M0q)^MW zipSm^=uc!ve!rq>Xdz!r&ZALZ<^t58F;`q_&BwWERf(rBMUJ+ai7ZoNI)1R}1Inh^ z^2a#wgz#$gnBX7so5IMo;m}Apg2Ts%*G3dxG!1@Dq)lN*8>}*NMrfYCe?vJw+)hSD z3KYQ`HF#F-rJ>L*1@HL$CF z$3@QM{iMUSuDs_}c4I;0zr&$B3L@AId{aRLdxhUv7{N~BcN9kcUKrX0Vd_Zr$YE); z4+u~^GxD*5P=ZlkU!ZwSI!60NGfS3)+6-1AIP$k}=(5P4!#EZG z*pE&MjTAYZZ8OZu^@71LFgMfD&NphF#YVv_m3!88oB9+ z@FRth+l~nTy)d%nh{&ywc|`c9;mFsIh*dRXTb3xV-c{UQdvLN!y zNa*>($di%Ky6~+9zmJ5z9*OLYguWAr?1?b$Q;!Ah2MZ#}&`&9cMGKRkW`0z!OzP~P z^KrF?Mpo&CSzCrSWTtU2k*-{$S-m-^!5X; z%xE_gXaXcC;P|x6M3Edb+^otOt^o@At33|oWI!v-3lOqjb{o{{RBU*2H2ax#3@b@C zA+iGW$2(anJ4ceG$W_ebh1908Z*X21Fv$#?rYl$S}`|R~FsBGMsOpmU7`@a(`uR5Wx#jas>KT^1PY1T^06zL#6^u{>8AEd@f zqGC{D317QOL-Ym5>L|D-xX@Lw63yn&c9AWB@ zo$O~-7&SB@Uo0%a%6~oVS$tT16ifc-wl$&JzPEU71P!W}YN-HC6n;H?44Ios_~lJe zx~T&F@>1rlElp7@bmCJZ5Y?tIy}^&OnN#EGOUto5npwZVY(mL**-hY=C2xiey!3pG z0DnC21m2Wl4HF&kU2C@w%>}mXy?2u`R`*QtI*wdXF5)Symj6bjacmfav;uZ-F%D8J z?RWqyEt&a-7^b31lmBZC5UaHx z!@W#=D)+`u>nX1i2h{?K%wN5_dzSqWc21oZ z+w-SbcnSkpgHgC`4;D0oVV?2egZ21)> zKgKAsA8_eH*mas#ZHp(O0K%8NY1_O9cX|c)Y9+UM_w0a@+q`L8z0__zKyj>ppXN>5 z&3e!)81)La6<>RA@wd0*R{#UoVEupSYQ0Xi!Rwl;gnTz+Brc@?p3CV*2)OSAL&*9) zgxu>*dyp?NxE|hFu*J4GT{b$CLdJ-JNsO`xJheWI6)1bpo`QZzut^QiP9A9*xqz(VIVCck#;%^^W zw!Va4i(n84G<9*&mPw_Y_bXAOAvGQbrzgga-hh;1QM371|5PExKnqdUeyP4kHWjAj zH}-Y;qm-W-tdzj-59Kr8;xQ8x^#5t#du$5w!!cBUtL>GL6T3zA4Dr5;4G+&ID23E1tG`hc|N8o4t{da*%CZ$Ny1q+Pd!lv-d6VQB~LaCm9AJU=pp` z_^JcO8hl_T1W0_;gk)fb3=l~G-$O_Si00K~0>LLXfQ}(bt8KN~iq^LDDfPBKs;yX1 zY-=mms#R+%ZM6l_g4S1UmH)T)UVCQEo^ya$?Y+H!_wRS+ocZ?JYpuQZ+RwAkIVa$% z6j$N?t-HeGHlcYc3=f`LTo6vKEJaj!aAg&p`v0^GNCjOcpReAH{T0}Eq}?-;Jg7L_ zyd|9Ttj^G2d>3uet3LNUnsKJX|N8Q*iOy+g49yNSL}xWNI8&Qi+u|*OL}#D}-zf|< zceb}Tb+kAU{n5lgPaE8pzRj0H+FLsmng*KsG69uFedIAk)6v-y*K9(4U2#;2>e;ia z&kA(MyYS-RPEc&sw%BB-5X%bq}B@>Vat`l{JChTc|xvJ!GXGYEbJn!B=*?NEx8}WojFmS}jp^a7l_$xVbJs zP4iB%C#Vy_7M_aX2nxB)x#m6g&z|LOK3{jo2OH#ze%(=T+d ziLxITt~_X0!HVA_!!*yH7#%>%hN9IEs-k>#Z5QQA+oLL)pZ##^*&e9>D?_KVzqgUzd-(uR{9^sRh^f zRPf;MH=uq>l3E_UAw|LKqtU@r-=I<@4bP#QycxNqdjTS=$Ou1BLA28qXSe*&Fl z>i*64IpDQ$O$6P3;f`mCLvlK>F5a;)GNd)ym}YN*?ME)<7}PeLhi3ogc5| zIvMuDdTH8TFC>mmF;cd=KNVZFV?B4ENN?^lJ!e$Y`YC^UlO9`%Bp;0?AJiRU|K67zQbxat{9?&GI?#g!dVP;c z?f;{<8z8nY6hx1NJ4`!Jc@K#s4?z`jF^0f)(nxX=ZULd6%Zv1X%1K;0E17r~En#^vMw(#Xd#hew zKP6m=d2i}Y3mk@;8rscwJlZRs}`=oy*Erc^nJG|e6MKxm1^pz$3W5K#U;ANTNBR6AQnATjG0Bb zlIijJTY7ws#^!-46d;=HC)FyuF7JWKk%4)6(ZMdWRa)P3-xy7HVK3)vW(tN5AwWgM zEz!k!No++VH{KiqPx2o)4ZIxx^Y}k+mTtZfdpT)Do%2dKhymBOaB_Qu>M?8^r76&R zT6gTsS@C%!jSiHcODkBBB(7-3s8-jf>s`T$wTMVn8MW{qOjQ}WBUG^B3z9-rs)7_n z5!cg;F=KzvxQ`hHqSrxd=Q3YXdxM_Q4+K`JDV?zb#=D2HZKd6g>*@p zr0R_Hm9d7dSbNij@feL>;w|{%$D$Ms;wMpjb7yZy0&(w4NQxWYH)DMR!$l=QE$K7A zB#=S0hd=mw_XMebSE3sqf9`5)YL1KVJ_O5aQs%Dj>}(4)G({T9Vxgg0a}SFA4@J>4 z|N9iC)zq9ED48~M&bUCu_zB~KfwIzIW$DDy$$=91ULf3*(D;ev3EC{WF!Kp2)ORFHIPR{>Ci(P~YbCR5+EcEb6E{kik= zsSPT|aTUi^(YZRma8+(?eo?=#Hb3y`{G!_Y!fH?wkM$IfP)O#X8&s*ft`jJSzXA8u^47=K823UxmHZZu~~ ze&MybQ}TE!H}%7_Am2Y?2Ac;@<|XVpN#25FnnaxveY$Y zl z@`J3p7ooaWOLnWxugd)eY8Vi!>P;Wj4SZ@AK5dlp+hT6Su1m=XdDZV}`SrP1xYlCi zgX(Y>^J>AhuEVf@?s=+#D+RQM<8P&S<*VsBh3b)8JyI1{s%DYB*Rc(#?A2x3SL^Zv z?J57HvJ8Ocb(XOa*V?b6`wyS8+E<6_unz4%=|S%rrLeDh6oZ!D4@x0OKT3x%)t71c zfm!)QQQd}b-%&kAA7VxeeM@sO@N+??EDDxYcl+l z?h*FQ%To1Y+9>3wChezA9ugqX)?!l@E^Ssgkqi4%LZL;etI*~7&}zq zdO6GgvcX}8YjV-{4EiF}pQE0v@`qKQd6sUrQ-cZRm&U%H@See8AsQ

RR^N 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 Date: Tue, 12 May 2020 14:04:03 -0700 Subject: [PATCH 058/362] fix centos8 build --- .../install/centos8/lib/libredis++.a | Bin 1168436 -> 1168436 bytes make-linux.mk | 9 ++++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/lib/libredis++.a b/ext/redis-plus-plus-1.1.1/install/centos8/lib/libredis++.a index c605b0ef0c1b0b43ccedb4b6969d3adb53897fe5..fbf88b667147cb995cc74a4cdf1c2766ff2ccf73 100644 GIT binary patch delta 275 zcmdn8!hOpM_X%<=rWQs9jY?aU7`M0p*#_p*<#q^&Z~y4RXt@}~x7@Dxf>9z5$TT#V z4rGdLPY7d5YKHK^O7Gre%G?d&8-nD;wB;q zc2)b2)4bb%oaTE}2okf{e$STw1z6e=BK=?{|Mmwn1*|~+HMHCgbl8mbK&BDcPvYAz JrVA$L007$-W6%Hq delta 270 zcmdn8!hOpM_X%<=#wNyQjY?aU7`M1E8=DwSpZI`7eESC%Mytg@DI>G(dM_9y^1#gL zK&IICgfOP0X0XbKr+LM<-?_<@wHv`rn9A(Lf#B|$%gl8Mq}gJ++ztWp?GNKwUJ8P& zG-y9p$O6QyK+Fcj?Ay;3avT8JV{9}XC?(Op>H!B3a{@6J5OZ%|^?)a#A1uF}uYmVL zG>B;ec2fI~)4bb%oaTE}2o~FZ&zAp1G|&ykV866Kn90BW!At=wh*!4*-7#Z5NTC@> Mv-tLl>4M2Q0H}^-!T Date: Tue, 12 May 2020 15:17:57 -0700 Subject: [PATCH 059/362] can now build centos8 docker container with Redis support --- .dockerignore | 2 ++ ext/central-controller-docker/Dockerfile | 10 +++--- ext/central-controller-docker/main.sh | 40 ++++++++++++------------ make-mac.mk | 4 +-- 4 files changed, 29 insertions(+), 27 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..4acef401c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.git/ +workspace/ diff --git a/ext/central-controller-docker/Dockerfile b/ext/central-controller-docker/Dockerfile index 04d4826c3..2866e6995 100644 --- a/ext/central-controller-docker/Dockerfile +++ b/ext/central-controller-docker/Dockerfile @@ -1,22 +1,22 @@ # Dockerfile for ZeroTier Central Controllers -FROM centos:7 as builder +FROM centos:8 as builder MAINTAINER Adam Ierymekno , Grant Limberg ARG git_branch=master RUN yum update -y -RUN yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm +RUN yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm && dnf -qy module disable postgresql RUN yum -y install epel-release && yum -y update && yum clean all RUN yum groupinstall -y "Development Tools" -RUN yum install -y bash postgresql10 postgresql10-devel libpqxx-devel glibc-static libstdc++-static clang jemalloc jemalloc-devel +RUN yum install -y bash postgresql10 postgresql10-devel libpqxx-devel clang jemalloc jemalloc-devel # RUN git clone http://git.int.zerotier.com/zerotier/ZeroTierOne.git # RUN if [ "$git_branch" != "master" ]; then cd ZeroTierOne && git checkout -b $git_branch origin/$git_branch; fi ADD . /ZeroTierOne RUN cd ZeroTierOne && make clean && make central-controller -FROM centos:7 -RUN yum install -y yum install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm && yum -y install epel-release && yum -y update && yum clean all +FROM centos:8 +RUN yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm && dnf -qy module disable postgresql && yum -y install epel-release && yum -y update && yum clean all RUN yum install -y jemalloc jemalloc-devel postgresql10 COPY --from=builder /ZeroTierOne/zerotier-one /usr/local/bin/zerotier-one diff --git a/ext/central-controller-docker/main.sh b/ext/central-controller-docker/main.sh index b8d3b142c..6f6a01f69 100755 --- a/ext/central-controller-docker/main.sh +++ b/ext/central-controller-docker/main.sh @@ -25,30 +25,30 @@ if [ -z "$ZT_DB_PASSWORD" ]; then exit 1 fi -RMQ="" -if [ "$ZT_USE_RABBITMQ" == "true" ]; then - if [ -z "$RABBITMQ_HOST" ]; then - echo '*** FAILED: RABBITMQ_HOST environment variable not defined' +REDIS="" +if [ "$ZT_USE_REDIS" == "true" ]; then + if [ -z "$ZT_REDIS_HOST" ]; then + echo '*** FAILED: ZT_REDIS_HOST environment variable not defined' exit 1 fi - if [ -z "$RABBITMQ_PORT" ]; then - echo '*** FAILED: RABBITMQ_PORT environment variable not defined' + + if [ -z "$ZT_REDIS_PORT" ]; then + echo '*** FAILED: ZT_REDIS_PORT enivronment variable not defined' exit 1 fi - if [ -z "$RABBITMQ_USERNAME" ]; then - echo '*** FAILED: RABBITMQ_USERNAME environment variable not defined' + + if [ -z "$ZT_REDIS_CLUSTER_MODE" ]; + echo '*** FAILED: ZT_REDIS_CLUSTER_MODE environment variable not defined' exit 1 fi - if [ -z "$RABBITMQ_PASSWORD" ]; then - echo '*** FAILED: RABBITMQ_PASSWORD environment variable not defined' - exit 1 - fi - RMQ=", \"rabbitmq\": { - \"host\": \"${RABBITMQ_HOST}\", - \"port\": ${RABBITMQ_PORT}, - \"username\": \"${RABBITMQ_USERNAME}\", - \"password\": \"${RABBITMQ_PASSWORD}\" - }" + + REDIS="\"redis\": { + \"hostname\": \"${ZT_REDIS_HOST}\", + \"port\": ${ZT_REDIS_PORT}, + \"clusterMode\": ${ZT_REDIS_CLUSTER_MODE}, + \"password\": \"${ZT_REDIS_PASSWORD}\" + } + " fi mkdir -p /var/lib/zerotier-one @@ -62,14 +62,14 @@ DEFAULT_PORT=9993 echo "{ \"settings\": { + \"controllerDbPath\": \"postgres:host=${ZT_DB_HOST} port=${ZT_DB_PORT} dbname=${ZT_DB_NAME} user=${ZT_DB_USER} password=${ZT_DB_PASSWORD} sslmode=prefer sslcert=${DB_CLIENT_CERT} sslkey=${DB_CLIENT_KEY} sslrootcert=${DB_SERVER_CA}\", \"portMappingEnabled\": true, \"softwareUpdate\": \"disable\", \"interfacePrefixBlacklist\": [ \"inot\", \"nat64\" ], - \"controllerDbPath\": \"postgres:host=${ZT_DB_HOST} port=${ZT_DB_PORT} dbname=${ZT_DB_NAME} user=${ZT_DB_USER} password=${ZT_DB_PASSWORD} sslmode=prefer sslcert=${DB_CLIENT_CERT} sslkey=${DB_CLIENT_KEY} sslrootcert=${DB_SERVER_CA}\" - ${RMQ} + ${REDIS} } } " > /var/lib/zerotier-one/local.conf diff --git a/make-mac.mk b/make-mac.mk index 5e7a67c20..f7ff8759b 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -152,8 +152,8 @@ official: FORCE make ZT_OFFICIAL_RELEASE=1 mac-dist-pkg central-controller-docker: FORCE - #docker build --no-cache -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . - docker build -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . + docker build --no-cache -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . + clean: rm -rf MacEthernetTapAgent *.dSYM build-* *.a *.pkg *.dmg *.o node/*.o controller/*.o service/*.o osdep/*.o ext/http-parser/*.o $(CORE_OBJS) $(ONE_OBJS) zerotier-one zerotier-idtool zerotier-selftest zerotier-cli zerotier doc/node_modules macui/build zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_* From 15c0c1db39f51591a27f599835ee2f9b606237f0 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 13 May 2020 09:46:41 -0700 Subject: [PATCH 060/362] finish the RabbitMQ-ectomy --- controller/PostgreSQL.cpp | 12 +++++++----- ext/central-controller-docker/main.sh | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 505d76527..286a734e0 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -128,8 +128,10 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, R opts.db = 0; poolOpts.size = 10; if (_rc->clusterMode) { + fprintf(stderr, "Using Redis in Cluster Mode\n"); _cluster = std::make_shared(opts, poolOpts); } else { + fprintf(stderr, "Using Redis in Standalone Mode\n"); _redis = std::make_shared(opts, poolOpts); } } @@ -613,7 +615,7 @@ void PostgreSQL::heartbeat() std::string build = std::to_string(ZEROTIER_ONE_VERSION_BUILD); std::string now = std::to_string(OSUtils::now()); std::string host_port = std::to_string(_listenPort); - std::string use_rabbitmq = (false) ? "true" : "false"; + std::string use_redis = (_rc != NULL) ? "true" : "false"; const char *values[10] = { controllerId, hostname, @@ -624,16 +626,16 @@ void PostgreSQL::heartbeat() rev.c_str(), build.c_str(), host_port.c_str(), - use_rabbitmq.c_str() + use_redis.c_str() }; PGresult *res = PQexecParams(conn, - "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_rabbitmq) " - "VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10) " + "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_rabbitmq, use_redis) " + "VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10, $11) " "ON CONFLICT (id) DO UPDATE SET cluster_host = EXCLUDED.cluster_host, last_alive = EXCLUDED.last_alive, " "public_identity = EXCLUDED.public_identity, v_major = EXCLUDED.v_major, v_minor = EXCLUDED.v_minor, " "v_rev = EXCLUDED.v_rev, v_build = EXCLUDED.v_rev, host_port = EXCLUDED.host_port, " - "use_rabbitmq = EXCLUDED.use_rabbitmq", + "use_redis = EXCLUDED.use_redis", 10, // number of parameters NULL, // oid field. ignore values, // values for substitution diff --git a/ext/central-controller-docker/main.sh b/ext/central-controller-docker/main.sh index 6f6a01f69..e0ebabae0 100755 --- a/ext/central-controller-docker/main.sh +++ b/ext/central-controller-docker/main.sh @@ -37,7 +37,7 @@ if [ "$ZT_USE_REDIS" == "true" ]; then exit 1 fi - if [ -z "$ZT_REDIS_CLUSTER_MODE" ]; + if [ -z "$ZT_REDIS_CLUSTER_MODE" ]; then echo '*** FAILED: ZT_REDIS_CLUSTER_MODE environment variable not defined' exit 1 fi @@ -49,6 +49,8 @@ if [ "$ZT_USE_REDIS" == "true" ]; then \"password\": \"${ZT_REDIS_PASSWORD}\" } " +else + REDIS="\"redis\": {}" fi mkdir -p /var/lib/zerotier-one From 701960def5e6066ba11097e1288fbeed02ec91cd Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 13 May 2020 17:23:27 -0700 Subject: [PATCH 061/362] Track member status in Redis --- controller/PostgreSQL.cpp | 138 +++++++++++++++++++++++++++++++++----- controller/PostgreSQL.hpp | 4 ++ 2 files changed, 124 insertions(+), 18 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 286a734e0..05d2de7b1 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -229,12 +229,14 @@ void PostgreSQL::eraseNetwork(const uint64_t networkId) tmp.first["objtype"] = "_delete_network"; tmp.second = true; _commitQueue.post(tmp); + nlohmann::json nullJson; + _networkChanged(tmp.first, nullJson, true); } void PostgreSQL::eraseMember(const uint64_t networkId, const uint64_t memberId) { char tmp2[24]; - std::pair tmp; + std::pair tmp, nw; Utils::hex(networkId, tmp2); tmp.first["nwid"] = tmp2; Utils::hex(memberId, tmp2); @@ -242,6 +244,8 @@ void PostgreSQL::eraseMember(const uint64_t networkId, const uint64_t memberId) tmp.first["objtype"] = "_delete_member"; tmp.second = true; _commitQueue.post(tmp); + nlohmann::json nullJson; + _memberChanged(tmp.first, nullJson, true); } void PostgreSQL::nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress &physicalAddress) @@ -630,8 +634,8 @@ void PostgreSQL::heartbeat() }; PGresult *res = PQexecParams(conn, - "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_rabbitmq, use_redis) " - "VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10, $11) " + "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_redis) " + "VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10) " "ON CONFLICT (id) DO UPDATE SET cluster_host = EXCLUDED.cluster_host, last_alive = EXCLUDED.last_alive, " "public_identity = EXCLUDED.public_identity, v_major = EXCLUDED.v_major, v_minor = EXCLUDED.v_minor, " "v_rev = EXCLUDED.v_rev, v_build = EXCLUDED.v_rev, host_port = EXCLUDED.host_port, " @@ -1401,6 +1405,15 @@ void PostgreSQL::commitThread() } void PostgreSQL::onlineNotificationThread() +{ + if (_rc != NULL) { + onlineNotification_Redis(); + } else { + onlineNotification_Postgres(); + } +} + +void PostgreSQL::onlineNotification_Postgres() { PGconn *conn = getPgConn(); if (PQstatus(conn) == CONNECTION_BAD) { @@ -1410,9 +1423,7 @@ void PostgreSQL::onlineNotificationThread() } _connected = 1; - //int64_t lastUpdatedNetworkStatus = 0; - std::unordered_map< std::pair,int64_t,_PairHasher > lastOnlineCumulative; - + nlohmann::json jtmp1, jtmp2; while (_run == 1) { if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "ERROR: Online Notification thread lost connection to Postgres."); @@ -1420,9 +1431,6 @@ void PostgreSQL::onlineNotificationThread() exit(5); } - // map used to send notifications to front end - std::unordered_map> updateMap; - std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; { std::lock_guard l(_lastOnline_l); @@ -1443,20 +1451,13 @@ void PostgreSQL::onlineNotificationThread() OSUtils::ztsnprintf(nwidTmp,sizeof(nwidTmp), "%.16llx", nwid_i); OSUtils::ztsnprintf(memTmp,sizeof(memTmp), "%.10llx", i->first.second); - auto found = _networks.find(nwid_i); - if (found == _networks.end()) { - continue; // skip members trying to join non-existant networks + if(!get(nwid_i, jtmp1, i->first.second, jtmp2)) { + continue; // skip non existent networks/members } std::string networkId(nwidTmp); std::string memberId(memTmp); - std::vector &members = updateMap[networkId]; - members.push_back(memberId); - - lastOnlineCumulative[i->first] = i->second.first; - - const char *qvals[2] = { networkId.c_str(), memberId.c_str() @@ -1526,6 +1527,107 @@ void PostgreSQL::onlineNotificationThread() } } +void PostgreSQL::onlineNotification_Redis() +{ + _connected = 1; + + char buf[11] = {0}; + std::string controllerId = std::string(_myAddress.toString(buf)); + + while (_run == 1) { + std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; + { + std::lock_guard l(_lastOnline_l); + lastOnline.swap(_lastOnline); + } + + if (_rc->clusterMode) { + auto tx = _cluster->redis(controllerId).transaction(true); + _doRedisUpdate(tx, controllerId, lastOnline); + } else { + auto tx = _redis->transaction(true); + _doRedisUpdate(tx, controllerId, lastOnline); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } +} + +void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId, + std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline) + +{ + nlohmann::json jtmp1, jtmp2; + for (auto i=lastOnline.begin(); i != lastOnline.end(); ++i) { + uint64_t nwid_i = i->first.first; + uint64_t memberid_i = i->first.second; + char nwidTmp[64]; + char memTmp[64]; + char ipTmp[64]; + OSUtils::ztsnprintf(nwidTmp,sizeof(nwidTmp), "%.16llx", nwid_i); + OSUtils::ztsnprintf(memTmp,sizeof(memTmp), "%.10llx", memberid_i); + + if (!get(nwid_i, jtmp1, memberid_i, jtmp2)){ + continue; // skip non existent members/networks + } + auto found = _networks.find(nwid_i); + if (found == _networks.end()) { + continue; // skip members trying to join non-existant networks + } + + std::string networkId(nwidTmp); + std::string memberId(memTmp); + + int64_t ts = i->second.first; + std::string ipAddr = i->second.second.toIpString(ipTmp); + std::string timestamp = std::to_string(ts); + + std::unordered_map record = { + {"id", memberId}, + {"address", ipAddr}, + {"last_updated", std::to_string(ts)} + }; + tx.zadd("nodes-online:{"+controllerId+"}", memberId, ts) + .zadd("network-nodes-online:{"+controllerId+"}:"+networkId, memberId, ts) + .sadd("network-nodes-all:{"+controllerId+"}:"+networkId, memberId) + .hmset("network:{"+controllerId+"}:"+networkId+":"+memberId, record.begin(), record.end()); + } + + tx.exec(); + + // expire records from all-nodes and network-nodes member list + uint64_t expireOld = OSUtils::now() - 300000; + + auto cursor = 0LL; + std::unordered_set keys; + // can't scan for keys in a transaction, so we need to fall back to _cluster or _redis + // to get all network-members keys + if(_rc->clusterMode) { + auto r = _cluster->redis(controllerId); + while(true) { + cursor = r.scan(cursor, "network-nodes-online:{"+controllerId+"}:*", INT_MAX, std::inserter(keys, keys.begin())); + if (cursor == 0) { + break; + } + } + } else { + while(true) { + cursor = _redis->scan(cursor, "network-nodes-online:"+controllerId+":*", INT_MAX, std::inserter(keys, keys.begin())); + if (cursor == 0) { + break; + } + } + } + + tx.zremrangebyscore("nodes-online:{"+controllerId+"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); + + for(const auto &k : keys) { + tx.zremrangebyscore(k, sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); + } + + tx.exec(); +} + PGconn *PostgreSQL::getPgConn(OverrideMode m) { if (m == ALLOW_PGBOUNCER_OVERRIDE) { diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index 44347cd81..f61670132 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -70,6 +70,10 @@ private: void commitThread(); void onlineNotificationThread(); + void onlineNotification_Postgres(); + void onlineNotification_Redis(); + void _doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId, + std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline); enum OverrideMode { ALLOW_PGBOUNCER_OVERRIDE = 0, From f8ba1962e634541f43df0aa7e55a56eb8fde65cd Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 14 May 2020 15:08:37 -0700 Subject: [PATCH 062/362] fix equals() methods --- .../zerotier/sdk/VirtualNetworkConfig.java | 64 ++++++++++--------- .../com/zerotier/sdk/VirtualNetworkRoute.java | 21 ++++-- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/java/src/com/zerotier/sdk/VirtualNetworkConfig.java b/java/src/com/zerotier/sdk/VirtualNetworkConfig.java index 64512dadd..0e1945dfa 100644 --- a/java/src/com/zerotier/sdk/VirtualNetworkConfig.java +++ b/java/src/com/zerotier/sdk/VirtualNetworkConfig.java @@ -32,6 +32,7 @@ import java.lang.Override; import java.lang.String; import java.util.ArrayList; import java.net.InetSocketAddress; +import java.util.Collections; public final class VirtualNetworkConfig implements Comparable { public static final int MAX_MULTICAST_SUBSCRIPTIONS = 4096; @@ -57,39 +58,42 @@ public final class VirtualNetworkConfig implements Comparable current = new ArrayList<>(); + ArrayList newConfig = new ArrayList<>(); + for (InetSocketAddress s : assignedAddresses) { + current.add(s.toString()); } - - boolean routesEqual = true; - if(routes.length == cfg.routes.length) { - for (int i = 0; i < routes.length; ++i) { - if (!routes[i].equals(cfg.routes[i])) { - routesEqual = false; - } - } - } else { - routesEqual = false; + for (InetSocketAddress s : cfg.assignedAddresses) { + newConfig.add(s.toString()); } + Collections.sort(current); + Collections.sort(newConfig); + boolean aaEqual = current.equals(newConfig); - return nwid == cfg.nwid && - mac == cfg.mac && - name.equals(cfg.name) && - status.equals(cfg.status) && - type.equals(cfg.type) && - mtu == cfg.mtu && - dhcp == cfg.dhcp && - bridge == cfg.bridge && - broadcastEnabled == cfg.broadcastEnabled && - portError == cfg.portError && - enabled == cfg.enabled && + current.clear(); + newConfig.clear(); + + for (VirtualNetworkRoute r : routes) { + current.add(r.toString()); + } + for (VirtualNetworkRoute r : cfg.routes) { + newConfig.add(r.toString()); + } + Collections.sort(current); + Collections.sort(newConfig); + boolean routesEqual = current.equals(newConfig); + + return this.nwid == cfg.nwid && + this.mac == cfg.mac && + this.name.equals(cfg.name) && + this.status.equals(cfg.status) && + this.type.equals(cfg.type) && + this.mtu == cfg.mtu && + this.dhcp == cfg.dhcp && + this.bridge == cfg.bridge && + this.broadcastEnabled == cfg.broadcastEnabled && + this.portError == cfg.portError && + this.enabled == cfg.enabled && aaEqual && routesEqual; } diff --git a/java/src/com/zerotier/sdk/VirtualNetworkRoute.java b/java/src/com/zerotier/sdk/VirtualNetworkRoute.java index b89dce7ba..51bdfef33 100644 --- a/java/src/com/zerotier/sdk/VirtualNetworkRoute.java +++ b/java/src/com/zerotier/sdk/VirtualNetworkRoute.java @@ -58,14 +58,23 @@ public final class VirtualNetworkRoute implements Comparable Date: Thu, 14 May 2020 20:09:25 -0700 Subject: [PATCH 063/362] Formatting --- node/Bond.cpp | 189 ++++++++++++++++++++++++++++++++++------ node/Bond.hpp | 168 +++++++++++++++++------------------ node/BondController.hpp | 26 +++--- node/Flow.hpp | 12 +-- node/Path.hpp | 6 +- osdep/Slave.hpp | 7 +- 6 files changed, 271 insertions(+), 137 deletions(-) diff --git a/node/Bond.cpp b/node/Bond.cpp index 9a5ab1df8..2f283a696 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -140,7 +140,7 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) } } } - //fprintf(stderr, "resultant _rrIdx=%d\n", _rrIdx); + fprintf(stderr, "_rrIdx=%d\n", _rrIdx); if (_paths[_bondedIdx[_rrIdx]]) { return _paths[_bondedIdx[_rrIdx]]; } @@ -246,7 +246,7 @@ void Bond::recordIncomingPacket(const SharedPtr& path, uint64_t packetId, } /** * Learn new flows and pro-actively create entries for them in the bond so - * that the next time we send a packet out that is part of a flow we know + * that the next time we send a packet out that is part of a flow we know * which path to use. */ if ((flowId != ZT_QOS_NO_FLOW) @@ -385,7 +385,7 @@ SharedPtr Bond::createFlow(const SharedPtr &path, int32_t flowId, un } if (_flows.size() >= ZT_FLOW_MAX_COUNT) { fprintf(stderr, "max number of flows reached (%d), forcibly forgetting oldest flow\n", ZT_FLOW_MAX_COUNT); - forgetFlowsWhenNecessary(0,true,now); + forgetFlowsWhenNecessary(0,true,now); } SharedPtr flow = new Flow(flowId, now); _flows[flowId] = flow; @@ -588,7 +588,7 @@ void Bond::sendQOS_MEASUREMENT(void *tPtr,const SharedPtr &path,const int6 } else { RR->sw->send(tPtr,outp,false); } - // Account for the fact that a VERB_QOS_MEASUREMENT was just sent. Reset timers. + // Account for the fact that a VERB_QOS_MEASUREMENT was just sent. Reset timers. path->_packetsReceivedSinceLastQoS = 0; path->_lastQoSMeasurement = now; } @@ -608,7 +608,7 @@ void Bond::processBackgroundTasks(void *tPtr, const int64_t now) //fprintf(stderr, "_lastFrame=%llu, suggestedMonitorInterval=%d, _dynamicPathMonitorInterval=%d\n", // (now-_lastFrame), suggestedMonitorInterval, _dynamicPathMonitorInterval); } - + if (_slaveMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC) { _shouldCollectPathStatistics = true; } @@ -673,7 +673,7 @@ void Bond::processBackgroundTasks(void *tPtr, const int64_t now) if (((now - _lastPathNegotiationCheck) > ZT_PATH_NEGOTIATION_CHECK_INTERVAL) && _allowPathNegotiation) { _lastPathNegotiationCheck = now; pathNegotiationCheck(tPtr, now); - } + } } void Bond::applyUserPrefs() @@ -854,8 +854,8 @@ void Bond::estimatePathQuality(const int64_t now) float plr[ZT_MAX_PEER_NETWORK_PATHS]; float per[ZT_MAX_PEER_NETWORK_PATHS]; float thr[ZT_MAX_PEER_NETWORK_PATHS]; - float thm[ZT_MAX_PEER_NETWORK_PATHS]; - float thv[ZT_MAX_PEER_NETWORK_PATHS]; + float thm[ZT_MAX_PEER_NETWORK_PATHS]; + float thv[ZT_MAX_PEER_NETWORK_PATHS]; float maxLAT = 0; float maxPDV = 0; @@ -867,7 +867,7 @@ void Bond::estimatePathQuality(const int64_t now) float quality[ZT_MAX_PEER_NETWORK_PATHS]; uint8_t alloc[ZT_MAX_PEER_NETWORK_PATHS]; - + float totQuality = 0.0f; memset(&lat, 0, sizeof(lat)); @@ -950,7 +950,7 @@ void Bond::estimatePathQuality(const int64_t now) //fprintf(stdout, "EH %d: lat=%8.3f, ltm=%8.3f, pdv=%8.3f, plr=%5.3f, per=%5.3f, thr=%8f, thm=%5.3f, thv=%5.3f, avl=%5.3f, age=%8.2f, scp=%4d, q=%5.3f, qtot=%5.3f, ac=%d if=%s, path=%s\n", // i, lat[i], ltm[i], pdv[i], plr[i], per[i], thr[i], thm[i], thv[i], avl[i], age[i], scp[i], quality[i], totQuality, alloc[i], getSlave(_paths[i])->ifname().c_str(), pathStr); - + } // Convert metrics to relative quantities and apply contribution weights for(unsigned int i=0;ibonded()) { alloc[i] = std::ceil((quality[i] / totQuality) * (float)255); @@ -1011,8 +1011,8 @@ void Bond::estimatePathQuality(const int64_t now) if (_paths[i]) { _paths[i]->address().toString(pathStr); fprintf(stdout, "%s, %s, %8.3f, %8.3f, %8.3f, %5.3f, %5.3f, %5.3f, %8f, %5.3f, %5.3f, %d, %5.3f, %d, %d, %d, %d, %d, %d, ", - getSlave(_paths[i])->ifname().c_str(), pathStr, _paths[i]->latencyMean, lat[i],pdv[i], _paths[i]->packetLossRatio, plr[i],per[i],thr[i],thm[i],thv[i],(now - _paths[i]->lastIn()),quality[i],alloc[i], - _paths[i]->relativeByteLoad, _paths[i]->assignedFlowCount, _paths[i]->alive(now, true), _paths[i]->eligible(now,_ackSendInterval), _paths[i]->qosStatsOut.size()); + getSlave(_paths[i])->ifname().c_str(), pathStr, _paths[i]->latencyMean, lat[i],pdv[i], _paths[i]->packetLossRatio, plr[i],per[i],thr[i],thm[i],thv[i],(now - _paths[i]->lastIn()),quality[i],alloc[i], + _paths[i]->relativeByteLoad, _paths[i]->assignedFlowCount, _paths[i]->alive(now, true), _paths[i]->eligible(now,_ackSendInterval), _paths[i]->qosStatsOut.size()); } } fprintf(stdout, "\n"); @@ -1022,7 +1022,144 @@ void Bond::estimatePathQuality(const int64_t now) void Bond::processBalanceTasks(const int64_t now) { - // Omitted + //fprintf(stderr, "processBalanceTasks\n"); + char curPathStr[128]; + if (_allowFlowHashing) { + /** + * Clean up and reset flows if necessary + */ + if ((now - _lastFlowExpirationCheck) > ZT_MULTIPATH_FLOW_CHECK_INTERVAL) { + Mutex::Lock _l(_flows_m); + forgetFlowsWhenNecessary(ZT_MULTIPATH_FLOW_EXPIRATION_INTERVAL,false,now); + _lastFlowExpirationCheck = now; + } + if ((now - _lastFlowStatReset) > ZT_FLOW_STATS_RESET_INTERVAL) { + Mutex::Lock _l(_flows_m); + _lastFlowStatReset = now; + std::map >::iterator it = _flows.begin(); + while (it != _flows.end()) { + it->second->resetByteCounts(); + ++it; + } + } + /** + * Re-allocate flows from dead paths + */ + if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_XOR || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE) { + Mutex::Lock _l(_flows_m); + for (int i=0;ieligible(now,_ackSendInterval) && _paths[i]->_shouldReallocateFlows) { + _paths[i]->address().toString(curPathStr); + fprintf(stderr, "%d reallocating flows from dead path %s on %s\n", (RR->node->now() - RR->bc->getBondStartTime()), curPathStr, getSlave(_paths[i])->ifname().c_str()); + std::map >::iterator flow_it = _flows.begin(); + while (flow_it != _flows.end()) { + if (flow_it->second->assignedPath() == _paths[i]) { + if(assignFlowToBondedPath(flow_it->second, now)) { + _paths[i]->_assignedFlowCount--; + } + } + ++flow_it; + } + _paths[i]->_shouldReallocateFlows = false; + } + } + } + } + /** + * Tasks specific to (Balance Round Robin) + */ + if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_RR) { + if (_allowFlowHashing) { + // TODO: Should ideally failover from (idx) to a random slave, this is so that (idx+1) isn't overloaded + } + else if (!_allowFlowHashing) { + // Nothing + } + } + /** + * Tasks specific to (Balance XOR) + */ + if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_XOR) { + // Nothing specific for XOR + } + /** + * Tasks specific to (Balance Aware) + */ + if ((_bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE)) { + if (_allowFlowHashing) { + Mutex::Lock _l(_flows_m); + /** + * Re-balance flows in proportion to slave capacity (or when eligibility changes) + */ + if ((now - _lastFlowRebalance) > ZT_FLOW_REBALANCE_INTERVAL) { + /** + * Determine "load" for bonded paths + */ + uint64_t totalBytes = 0; + for(unsigned int i=0;ibonded()) { + _paths[i]->_byteLoad = 0; + std::map >::iterator flow_it = _flows.begin(); + while (flow_it != _flows.end()) { + if (flow_it->second->assignedPath() == _paths[i]) { + _paths[i]->_byteLoad += flow_it->second->totalBytes(); + } + ++flow_it; + } + totalBytes += _paths[i]->_byteLoad; + } + } + /** + * Determine "affinity" for bonded path + */ + //fprintf(stderr, "\n\n"); + _totalBondUnderload = 0; + + for(unsigned int i=0;ibonded()) { + if (totalBytes) { + uint8_t relativeByteLoad = std::ceil(((float)_paths[i]->_byteLoad / (float)totalBytes) * (float)255); + //fprintf(stderr, "lastComputedAllocation = %d\n", _paths[i]->allocation); + //fprintf(stderr, " relativeByteLoad = %d\n", relativeByteLoad); + _paths[i]->_relativeByteLoad = relativeByteLoad; + uint8_t relativeUnderload = std::max(0, (int)_paths[i]->_allocation - (int)relativeByteLoad); + //fprintf(stderr, " relativeUnderload = %d\n", relativeUnderload); + _totalBondUnderload += relativeUnderload; + //fprintf(stderr, " _totalBondUnderload = %d\n\n", _totalBondUnderload); + //_paths[i]->affinity = (relativeUnderload > 0 ? relativeUnderload : _paths[i]->_allocation); + } + else { // set everything to base values + _totalBondUnderload = 0; + //_paths[i]->affinity = 0; + } + } + } + + //fprintf(stderr, "_totalBondUnderload=%d (end)\n\n", _totalBondUnderload); + + /** + * + */ + //fprintf(stderr, "_lastFlowRebalance\n"); + std::map >::iterator it = _flows.begin(); + while (it != _flows.end()) { + int32_t flowId = it->first; + SharedPtr flow = it->second; + if ((now - flow->_lastPathReassignment) > ZT_FLOW_MIN_REBALANCE_INTERVAL) { + //fprintf(stdout, " could move : %x\n", flowId); + } + ++it; + } + _lastFlowRebalance = now; + } + } + else if (!_allowFlowHashing) { + // Nothing + } + } } void Bond::dequeueNextActiveBackupPath(const uint64_t now) @@ -1042,7 +1179,7 @@ void Bond::dequeueNextActiveBackupPath(const uint64_t now) } void Bond::processActiveBackupTasks(const int64_t now) -{ +{ //fprintf(stderr, "%llu processActiveBackupTasks\n", (now - RR->bc->getBondStartTime())); char pathStr[128]; char prevPathStr[128]; char curPathStr[128]; @@ -1058,7 +1195,7 @@ void Bond::processActiveBackupTasks(const int64_t now) /** * [Automatic mode] * The user has not explicitly specified slaves or their failover schedule, - * the bonding policy will now select the first eligible path and set it as + * the bonding policy will now select the first eligible path and set it as * its active backup path, if a substantially better path is detected the bonding * policy will assign it as the new active backup path. If the path fails it will * simply find the next eligible path. @@ -1187,9 +1324,9 @@ void Bond::processActiveBackupTasks(const int64_t now) } SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); _paths[i]->address().toString(pathStr); - + int failoverScoreHandicap = _paths[i]->_failoverScore; - if (_paths[i]->preferred()) + if (_paths[i]->preferred()) { failoverScoreHandicap += ZT_MULTIPATH_FAILOVER_HANDICAP_PREFERRED; //fprintf(stderr, "%s on %s ----> %d for preferred\n", pathStr, _paths[i]->ifname().c_str(), failoverScoreHandicap); @@ -1264,7 +1401,7 @@ void Bond::processActiveBackupTasks(const int64_t now) if (_paths[i].ptr() == negotiatedPath.ptr()) { _paths[i]->_negotiated = true; failoverScoreHandicap = ZT_MULTIPATH_FAILOVER_HANDICAP_NEGOTIATED; - } else { + } else { _paths[i]->_negotiated = false; } _paths[i]->_failoverScore = _paths[i]->_allocation + failoverScoreHandicap; @@ -1386,7 +1523,7 @@ void Bond::setReasonableDefaults(int policy) _lastPathNegotiationReceived=0; _lastBackgroundTaskCheck=0; _lastPathNegotiationCheck=0; - + _lastFlowStatReset=0; _lastFlowExpirationCheck=0; _localUtility=0; @@ -1397,7 +1534,7 @@ void Bond::setReasonableDefaults(int policy) _pathNegotiationCutoffCount=0; _lastFlowRebalance=0; _totalBondUnderload = 0; - + //_maxAcceptableLatency _maxAcceptablePacketDelayVariance = 50; _maxAcceptablePacketLossRatio = 0.10; @@ -1445,7 +1582,7 @@ void Bond::setReasonableDefaults(int policy) case ZT_BONDING_POLICY_BALANCE_RR: _failoverInterval = 5000; _allowFlowHashing = false; - _packetsPerSlave = 8; + _packetsPerSlave = 512; _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; _qualityWeights[ZT_QOS_LAT_IDX] = 0.4f; _qualityWeights[ZT_QOS_LTM_IDX] = 0.0f; @@ -1550,8 +1687,8 @@ void Bond::setUserQualityWeights(float weights[], int len) bool Bond::relevant() { - return _peer->identity().address().toInt() == 0x16a03a3d03 - || _peer->identity().address().toInt() == 0x4410300d03 + return _peer->identity().address().toInt() == 0x16a03a3d03 + || _peer->identity().address().toInt() == 0x4410300d03 || _peer->identity().address().toInt() == 0x795cbf86fa; } @@ -1566,7 +1703,7 @@ void Bond::dumpInfo(const int64_t now) //char oldPathStr[128]; char currPathStr[128]; - if (!relevant()) { + if (!relevant()) { return; } /* @@ -1589,7 +1726,7 @@ void Bond::dumpInfo(const int64_t now) } _lastPrintTS = now; _lastLogTS = now; - + fprintf(stderr, "\n\n"); for(int i=0; i getSlave(const SharedPtr& path); + SharedPtr getSlave(const SharedPtr& path); - /** - * Constructor. For use only in first initialization in Node - * - * @param renv Runtime environment - */ - Bond(const RuntimeEnvironment *renv); + /** + * Constructor. For use only in first initialization in Node + * + * @param renv Runtime environment + */ + Bond(const RuntimeEnvironment *renv); - /** - * Constructor. Creates a bond based off of ZT defaults - * - * @param renv Runtime environment - * @param policy Bonding policy - * @param peer - */ - Bond(const RuntimeEnvironment *renv, int policy, const SharedPtr& peer); + /** + * Constructor. Creates a bond based off of ZT defaults + * + * @param renv Runtime environment + * @param policy Bonding policy + * @param peer + */ + Bond(const RuntimeEnvironment *renv, int policy, const SharedPtr& peer); - /** - * Constructor. For use when user intends to manually specify parameters - * - * @param basePolicy - * @param policyAlias - * @param peer - */ - Bond(std::string& basePolicy, std::string& policyAlias, const SharedPtr& peer); + /** + * Constructor. For use when user intends to manually specify parameters + * + * @param basePolicy + * @param policyAlias + * @param peer + */ + Bond(std::string& basePolicy, std::string& policyAlias, const SharedPtr& peer); - /** - * Constructor. Creates a bond based off of a user-defined bond template - * - * @param renv Runtime environment - * @param original - * @param peer - */ - Bond(const RuntimeEnvironment *renv, const Bond &original, const SharedPtr& peer); + /** + * Constructor. Creates a bond based off of a user-defined bond template + * + * @param renv Runtime environment + * @param original + * @param peer + */ + Bond(const RuntimeEnvironment *renv, const Bond &original, const SharedPtr& peer); /** * @@ -101,7 +101,7 @@ public: * @param now Current time */ void nominatePath(const SharedPtr& path, int64_t now); - + /** * Propagate and memoize often-used bonding preferences for each path */ @@ -109,9 +109,9 @@ public: /** * Check path states and perform bond rebuilds if needed. - * + * * @param now Current time - * @param rebuild Whether or not the bond should be reconstructed. + * @param rebuild Whether or not the bond should be reconstructed. */ void curateBond(const int64_t now, bool rebuild); @@ -156,7 +156,7 @@ public: /** * Process the contents of an inbound VERB_ACK to gather path quality observations. - * + * * @param path Path over which packet was received * @param now Current time * @param ackedBytes Number of bytes ACKed by this VERB_ACK @@ -174,7 +174,7 @@ public: /** * Record statistics for an inbound packet. - * + * * @param path Path over which packet was received * @param packetId Packet ID * @param payloadLength Packet data length @@ -183,7 +183,7 @@ public: * @param now Current time */ void recordIncomingPacket(const SharedPtr& path, uint64_t packetId, uint16_t payloadLength, - Packet::Verb verb, int32_t flowId, int64_t now); + Packet::Verb verb, int32_t flowId, int64_t now); /** * Determines the most appropriate path for packet and flow egress. This decision is made by @@ -197,7 +197,7 @@ public: /** * Creates a new flow record - * + * * @param path Path over which flow shall be handled * @param flowId Flow ID * @param entropy A byte of entropy to be used by the bonding algorithm @@ -208,7 +208,7 @@ public: /** * Removes flow records that are past a certain age limit. - * + * * @param age Age threshold to be forgotten * @param oldest Whether only the oldest shall be forgotten * @param now Current time @@ -217,17 +217,17 @@ public: /** * Assigns a new flow to a bonded path - * + * * @param flow Flow to be assigned * @param now Current time */ bool assignFlowToBondedPath(SharedPtr &flow, int64_t now); - /** + /** * Determine whether a path change should occur given the remote peer's reported utility and our * local peer's known utility. This has the effect of assigning inbound and outbound traffic to - * the same path. - * + * the same path. + * * @param now Current time * @param path Path over which the negotiation request was received * @param remoteUtility How much utility the remote peer claims to gain by using the declared path @@ -245,7 +245,7 @@ public: /** * Sends a VERB_ACK to the remote peer. - * + * * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call * @param path Path over which packet should be sent * @param localSocket Local source socket @@ -253,11 +253,11 @@ public: * @param now Current time */ void sendACK(void *tPtr,const SharedPtr &path,int64_t localSocket, - const InetAddress &atAddress,int64_t now); + const InetAddress &atAddress,int64_t now); /** * Sends a VERB_QOS_MEASUREMENT to the remote peer. - * + * * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call * @param path Path over which packet should be sent * @param localSocket Local source socket @@ -265,11 +265,11 @@ public: * @param now Current time */ void sendQOS_MEASUREMENT(void *tPtr,const SharedPtr &path,int64_t localSocket, - const InetAddress &atAddress,int64_t now); + const InetAddress &atAddress,int64_t now); /** * Sends a VERB_PATH_NEGOTIATION_REQUEST to the remote peer. - * + * * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call * @param path Path over which packet should be sent */ @@ -280,10 +280,10 @@ public: * @param now Current time */ void processBalanceTasks(int64_t now); - + /** * Perform periodic tasks unique to active-backup - * + * * @param now Current time */ void processActiveBackupTasks(int64_t now); @@ -296,12 +296,12 @@ public: */ void dequeueNextActiveBackupPath(uint64_t now); - /** - * Set bond parameters to reasonable defaults, these may later be overwritten by + /** + * Set bond parameters to reasonable defaults, these may later be overwritten by * user-specified parameters. - * - * @param policy Bonding policy - */ + * + * @param policy Bonding policy + */ void setReasonableDefaults(int policy); /** @@ -450,19 +450,19 @@ public: */ inline uint16_t getUpDelay() { return _upDelay; } - /** - * @param upDelay Length of time before a newly-discovered path is admitted to the bond - */ + /** + * @param upDelay Length of time before a newly-discovered path is admitted to the bond + */ inline void setUpDelay(int upDelay) { if (upDelay >= 0) { _upDelay = upDelay; } } - /** - * @return Length of time before a newly-failed path is removed from the bond - */ + /** + * @return Length of time before a newly-failed path is removed from the bond + */ inline uint16_t getDownDelay() { return _downDelay; } - /** - * @param downDelay Length of time before a newly-failed path is removed from the bond - */ + /** + * @param downDelay Length of time before a newly-failed path is removed from the bond + */ inline void setDownDelay(int downDelay) { if (downDelay >= 0) { _downDelay = downDelay; } } /** @@ -470,11 +470,11 @@ public: */ inline uint16_t getBondMonitorInterval() { return _bondMonitorInterval; } - /** - * Set the current monitoring interval for the bond (can be overridden with intervals specific to certain slaves.) - * - * @param monitorInterval How often gratuitous VERB_HELLO(s) are sent to remote peer. - */ + /** + * Set the current monitoring interval for the bond (can be overridden with intervals specific to certain slaves.) + * + * @param monitorInterval How often gratuitous VERB_HELLO(s) are sent to remote peer. + */ inline void setBondMonitorInterval(uint16_t interval) { _bondMonitorInterval = interval; } /** @@ -487,10 +487,10 @@ public: */ inline uint8_t getPolicy() { return _bondingPolicy; } - /** - * - * @param allowFlowHashing - */ + /** + * + * @param allowFlowHashing + */ inline void setFlowHashing(bool allowFlowHashing) { _allowFlowHashing = allowFlowHashing; } /** @@ -498,10 +498,10 @@ public: */ bool flowHashingEnabled() { return _allowFlowHashing; } - /** - * - * @param packetsPerSlave - */ + /** + * + * @param packetsPerSlave + */ inline void setPacketsPerSlave(int packetsPerSlave) { _packetsPerSlave = packetsPerSlave; } /** @@ -514,7 +514,7 @@ public: * * @return */ - inline uint8_t getSlaveSelectMethod() { return _abSlaveSelectMethod; } + inline uint8_t getSlaveSelectMethod() { return _abSlaveSelectMethod; } /** * diff --git a/node/BondController.hpp b/node/BondController.hpp index c8fa660b0..acc70d2ff 100644 --- a/node/BondController.hpp +++ b/node/BondController.hpp @@ -55,10 +55,10 @@ public: */ bool inUse() { return !_bondPolicyTemplates.empty() || _defaultBondingPolicy; } - /** - * @param basePolicyName Bonding policy name (See ZeroTierOne.h) - * @return The bonding policy code for a given human-readable bonding policy name - */ + /** + * @param basePolicyName Bonding policy name (See ZeroTierOne.h) + * @return The bonding policy code for a given human-readable bonding policy name + */ static int getPolicyCodeByStr(const std::string& basePolicyName) { if (basePolicyName == "active-backup") { return 1; } @@ -83,18 +83,18 @@ public: return "none"; } - /** - * Sets the default bonding policy for new or undefined bonds. + /** + * Sets the default bonding policy for new or undefined bonds. * - * @param bp Bonding policy - */ + * @param bp Bonding policy + */ void setBondingLayerDefaultPolicy(uint8_t bp) { _defaultBondingPolicy = bp; } - /** - * Sets the default (custom) bonding policy for new or undefined bonds. + /** + * Sets the default (custom) bonding policy for new or undefined bonds. * - * @param alias Human-readable string alias for bonding policy - */ + * @param alias Human-readable string alias for bonding policy + */ void setBondingLayerDefaultPolicyStr(std::string alias) { _defaultBondingPolicyStr = alias; } /** @@ -119,7 +119,7 @@ public: bool addCustomPolicy(const SharedPtr& newBond); /** - * Assigns a specific bonding policy + * Assigns a specific bonding policy * * @param identity * @param policyAlias diff --git a/node/Flow.hpp b/node/Flow.hpp index cb8c3e4aa..5994a4fb2 100644 --- a/node/Flow.hpp +++ b/node/Flow.hpp @@ -24,10 +24,10 @@ namespace ZeroTier { */ struct Flow { - /** - * @param flowId Given flow ID - * @param now Current time - */ + /** + * @param flowId Given flow ID + * @param now Current time + */ Flow(int32_t flowId, int64_t now) : _flowId(flowId), _bytesInPerUnitTime(0), @@ -50,12 +50,12 @@ struct Flow * @return The Flow's ID */ int32_t id() { return _flowId; } - + /** * @return Number of incoming bytes processed on this flow per unit time */ int64_t bytesInPerUnitTime() { return _bytesInPerUnitTime; } - + /** * Record number of incoming bytes on this flow * diff --git a/node/Path.hpp b/node/Path.hpp index 9c54f718f..22a932edf 100644 --- a/node/Path.hpp +++ b/node/Path.hpp @@ -28,7 +28,6 @@ #include "Utils.hpp" #include "Packet.hpp" #include "RingBuffer.hpp" -//#include "Bond.hpp" #include "../osdep/Slave.hpp" @@ -48,7 +47,6 @@ class Path { friend class SharedPtr; friend class Bond; - //friend class SharedPtr; public: /** @@ -361,7 +359,7 @@ public: * @return the age of the path in terms of receiving packets */ inline int64_t age(int64_t now) { return (now - _lastIn); } - + /** * @return Time last trust-established packet was received */ @@ -634,7 +632,7 @@ private: * The variance in the estimated throughput of this path. */ float _throughputVariance; - + /** * The relative quality of this path to all others in the bond, [0-255]. */ diff --git a/osdep/Slave.hpp b/osdep/Slave.hpp index b1ae326ea..a4caa983f 100644 --- a/osdep/Slave.hpp +++ b/osdep/Slave.hpp @@ -61,7 +61,7 @@ public: _userSpecifiedAlloc(userSpecifiedAlloc), _isUserSpecified(false) {} - + /** * @return The string representation of this slave's underlying interface's system name. */ @@ -163,7 +163,7 @@ private: /** * What preference (if any) a user has for IP protocol version used in * path aggregations. Preference is expressed in the order of the digits: - * + * * 0: no preference * 4: IPv4 only * 6: IPv6 only @@ -212,7 +212,7 @@ private: uint8_t _mode; /** - * The specific name of the interface to be used in the event that this + * The specific name of the interface to be used in the event that this * slave fails. */ std::string _failoverToSlaveStr; @@ -230,7 +230,6 @@ private: bool _isUserSpecified; AtomicCounter __refCount; - }; } // namespace ZeroTier From 844725237dce8a5a26a7b8792b9c37860ed23fe5 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 18 May 2020 10:31:17 -0700 Subject: [PATCH 064/362] Add new ZT_ result codes that were added --- java/jni/ZT_jniutils.cpp | 13 +++++++++++-- java/src/com/zerotier/sdk/ResultCode.java | 15 ++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/java/jni/ZT_jniutils.cpp b/java/jni/ZT_jniutils.cpp index c52a2066b..3f7047795 100644 --- a/java/jni/ZT_jniutils.cpp +++ b/java/jni/ZT_jniutils.cpp @@ -44,6 +44,7 @@ jobject createResultObject(JNIEnv *env, ZT_ResultCode code) switch(code) { case ZT_RESULT_OK: + case ZT_RESULT_OK_IGNORED: LOGV("ZT_RESULT_OK"); fieldName = "RESULT_OK"; break; @@ -56,12 +57,20 @@ jobject createResultObject(JNIEnv *env, ZT_ResultCode code) fieldName = "RESULT_FATAL_ERROR_DATA_STORE_FAILED"; break; case ZT_RESULT_ERROR_NETWORK_NOT_FOUND: - LOGV("RESULT_FATAL_ERROR_DATA_STORE_FAILED"); + LOGV("ZT_RESULT_ERROR_NETWORK_NOT_FOUND"); fieldName = "RESULT_ERROR_NETWORK_NOT_FOUND"; break; + case ZT_RESULT_ERROR_UNSUPPORTED_OPERATION: + LOGV("ZT_RESULT_ERROR_UNSUPPORTED_OPERATION"); + fieldName = "RESULT_ERROR_UNSUPPORTED_OPERATION"; + break; + case ZT_RESULT_ERROR_BAD_PARAMETER: + LOGV("ZT_RESULT_ERROR_BAD_PARAMETER"); + fieldName = "ZT_RESULT_ERROR_BAD_PARAMETER"; + break; case ZT_RESULT_FATAL_ERROR_INTERNAL: default: - LOGV("RESULT_FATAL_ERROR_DATA_STORE_FAILED"); + LOGV("ZT_RESULT_FATAL_ERROR_INTERNAL"); fieldName = "RESULT_FATAL_ERROR_INTERNAL"; break; } diff --git a/java/src/com/zerotier/sdk/ResultCode.java b/java/src/com/zerotier/sdk/ResultCode.java index 5da82b319..66f575613 100644 --- a/java/src/com/zerotier/sdk/ResultCode.java +++ b/java/src/com/zerotier/sdk/ResultCode.java @@ -45,30 +45,35 @@ public enum ResultCode { /** * Ran out of memory */ - RESULT_FATAL_ERROR_OUT_OF_MEMORY(1), + RESULT_FATAL_ERROR_OUT_OF_MEMORY(100), /** * Data store is not writable or has failed */ - RESULT_FATAL_ERROR_DATA_STORE_FAILED(2), + RESULT_FATAL_ERROR_DATA_STORE_FAILED(101), /** * Internal error (e.g. unexpected exception indicating bug or build problem) */ - RESULT_FATAL_ERROR_INTERNAL(3), + RESULT_FATAL_ERROR_INTERNAL(102), // non-fatal errors /** * Network ID not valid */ - RESULT_ERROR_NETWORK_NOT_FOUND(1000); + RESULT_ERROR_NETWORK_NOT_FOUND(1000), + + RESULT_ERROR_UNSUPPORTED_OPERATION(1001), + + RESULT_ERROR_BAD_PARAMETER(1002); + private final int id; ResultCode(int id) { this.id = id; } public int getValue() { return id; } public boolean isFatal(int id) { - return (id > 0 && id < 1000); + return (id > 100 && id < 1000); } } \ No newline at end of file From 8ac42b4600c7dc81ed8c50a38af6640b1e450344 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 19 May 2020 17:40:11 -0700 Subject: [PATCH 065/362] controller status query fix --- controller/PostgreSQL.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 286a734e0..b8fd749a5 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -630,8 +630,8 @@ void PostgreSQL::heartbeat() }; PGresult *res = PQexecParams(conn, - "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_rabbitmq, use_redis) " - "VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10, $11) " + "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port,use_redis) " + "VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10) " "ON CONFLICT (id) DO UPDATE SET cluster_host = EXCLUDED.cluster_host, last_alive = EXCLUDED.last_alive, " "public_identity = EXCLUDED.public_identity, v_major = EXCLUDED.v_major, v_minor = EXCLUDED.v_minor, " "v_rev = EXCLUDED.v_rev, v_build = EXCLUDED.v_rev, host_port = EXCLUDED.host_port, " From ee91c8179900f4b74cdba1ecad9fc318c6a8e945 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 19 May 2020 17:40:51 -0700 Subject: [PATCH 066/362] Initialize C arrays to NULL in NetworkConfig() constructor --- node/NetworkConfig.cpp | 8 ++++---- node/NetworkConfig.hpp | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index e45a111d2..97985c7af 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -22,7 +22,7 @@ namespace ZeroTier { bool NetworkConfig::toDictionary(Dictionary &d,bool includeLegacy) const { Buffer *tmp = new Buffer(); - char tmp2[128]; + char tmp2[128] = {0}; try { d.clear(); @@ -84,7 +84,7 @@ bool NetworkConfig::toDictionary(Dictionary &d,b if (((int)lastrt < 32)||(lastrt == ZT_NETWORK_RULE_MATCH_ETHERTYPE)) { if (ets.length() > 0) ets.push_back(','); - char tmp2[16]; + char tmp2[16] = {0}; ets.append(Utils::hex((uint16_t)et,tmp2)); } et = 0; @@ -104,7 +104,7 @@ bool NetworkConfig::toDictionary(Dictionary &d,b if ((this->specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) { if (ab.length() > 0) ab.push_back(','); - char tmp2[16]; + char tmp2[16] = {0}; ab.append(Address(this->specialists[i]).toString(tmp2)); } } @@ -220,7 +220,7 @@ bool NetworkConfig::fromDictionary(const Dictionary Date: Wed, 13 May 2020 17:23:27 -0700 Subject: [PATCH 067/362] Track member status in Redis --- controller/PostgreSQL.cpp | 136 +++++++++++++++++++++++++++++++++----- controller/PostgreSQL.hpp | 4 ++ 2 files changed, 123 insertions(+), 17 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index b8fd749a5..05d2de7b1 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -229,12 +229,14 @@ void PostgreSQL::eraseNetwork(const uint64_t networkId) tmp.first["objtype"] = "_delete_network"; tmp.second = true; _commitQueue.post(tmp); + nlohmann::json nullJson; + _networkChanged(tmp.first, nullJson, true); } void PostgreSQL::eraseMember(const uint64_t networkId, const uint64_t memberId) { char tmp2[24]; - std::pair tmp; + std::pair tmp, nw; Utils::hex(networkId, tmp2); tmp.first["nwid"] = tmp2; Utils::hex(memberId, tmp2); @@ -242,6 +244,8 @@ void PostgreSQL::eraseMember(const uint64_t networkId, const uint64_t memberId) tmp.first["objtype"] = "_delete_member"; tmp.second = true; _commitQueue.post(tmp); + nlohmann::json nullJson; + _memberChanged(tmp.first, nullJson, true); } void PostgreSQL::nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress &physicalAddress) @@ -630,7 +634,7 @@ void PostgreSQL::heartbeat() }; PGresult *res = PQexecParams(conn, - "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port,use_redis) " + "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_redis) " "VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10) " "ON CONFLICT (id) DO UPDATE SET cluster_host = EXCLUDED.cluster_host, last_alive = EXCLUDED.last_alive, " "public_identity = EXCLUDED.public_identity, v_major = EXCLUDED.v_major, v_minor = EXCLUDED.v_minor, " @@ -1401,6 +1405,15 @@ void PostgreSQL::commitThread() } void PostgreSQL::onlineNotificationThread() +{ + if (_rc != NULL) { + onlineNotification_Redis(); + } else { + onlineNotification_Postgres(); + } +} + +void PostgreSQL::onlineNotification_Postgres() { PGconn *conn = getPgConn(); if (PQstatus(conn) == CONNECTION_BAD) { @@ -1410,9 +1423,7 @@ void PostgreSQL::onlineNotificationThread() } _connected = 1; - //int64_t lastUpdatedNetworkStatus = 0; - std::unordered_map< std::pair,int64_t,_PairHasher > lastOnlineCumulative; - + nlohmann::json jtmp1, jtmp2; while (_run == 1) { if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "ERROR: Online Notification thread lost connection to Postgres."); @@ -1420,9 +1431,6 @@ void PostgreSQL::onlineNotificationThread() exit(5); } - // map used to send notifications to front end - std::unordered_map> updateMap; - std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; { std::lock_guard l(_lastOnline_l); @@ -1443,20 +1451,13 @@ void PostgreSQL::onlineNotificationThread() OSUtils::ztsnprintf(nwidTmp,sizeof(nwidTmp), "%.16llx", nwid_i); OSUtils::ztsnprintf(memTmp,sizeof(memTmp), "%.10llx", i->first.second); - auto found = _networks.find(nwid_i); - if (found == _networks.end()) { - continue; // skip members trying to join non-existant networks + if(!get(nwid_i, jtmp1, i->first.second, jtmp2)) { + continue; // skip non existent networks/members } std::string networkId(nwidTmp); std::string memberId(memTmp); - std::vector &members = updateMap[networkId]; - members.push_back(memberId); - - lastOnlineCumulative[i->first] = i->second.first; - - const char *qvals[2] = { networkId.c_str(), memberId.c_str() @@ -1526,6 +1527,107 @@ void PostgreSQL::onlineNotificationThread() } } +void PostgreSQL::onlineNotification_Redis() +{ + _connected = 1; + + char buf[11] = {0}; + std::string controllerId = std::string(_myAddress.toString(buf)); + + while (_run == 1) { + std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; + { + std::lock_guard l(_lastOnline_l); + lastOnline.swap(_lastOnline); + } + + if (_rc->clusterMode) { + auto tx = _cluster->redis(controllerId).transaction(true); + _doRedisUpdate(tx, controllerId, lastOnline); + } else { + auto tx = _redis->transaction(true); + _doRedisUpdate(tx, controllerId, lastOnline); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } +} + +void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId, + std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline) + +{ + nlohmann::json jtmp1, jtmp2; + for (auto i=lastOnline.begin(); i != lastOnline.end(); ++i) { + uint64_t nwid_i = i->first.first; + uint64_t memberid_i = i->first.second; + char nwidTmp[64]; + char memTmp[64]; + char ipTmp[64]; + OSUtils::ztsnprintf(nwidTmp,sizeof(nwidTmp), "%.16llx", nwid_i); + OSUtils::ztsnprintf(memTmp,sizeof(memTmp), "%.10llx", memberid_i); + + if (!get(nwid_i, jtmp1, memberid_i, jtmp2)){ + continue; // skip non existent members/networks + } + auto found = _networks.find(nwid_i); + if (found == _networks.end()) { + continue; // skip members trying to join non-existant networks + } + + std::string networkId(nwidTmp); + std::string memberId(memTmp); + + int64_t ts = i->second.first; + std::string ipAddr = i->second.second.toIpString(ipTmp); + std::string timestamp = std::to_string(ts); + + std::unordered_map record = { + {"id", memberId}, + {"address", ipAddr}, + {"last_updated", std::to_string(ts)} + }; + tx.zadd("nodes-online:{"+controllerId+"}", memberId, ts) + .zadd("network-nodes-online:{"+controllerId+"}:"+networkId, memberId, ts) + .sadd("network-nodes-all:{"+controllerId+"}:"+networkId, memberId) + .hmset("network:{"+controllerId+"}:"+networkId+":"+memberId, record.begin(), record.end()); + } + + tx.exec(); + + // expire records from all-nodes and network-nodes member list + uint64_t expireOld = OSUtils::now() - 300000; + + auto cursor = 0LL; + std::unordered_set keys; + // can't scan for keys in a transaction, so we need to fall back to _cluster or _redis + // to get all network-members keys + if(_rc->clusterMode) { + auto r = _cluster->redis(controllerId); + while(true) { + cursor = r.scan(cursor, "network-nodes-online:{"+controllerId+"}:*", INT_MAX, std::inserter(keys, keys.begin())); + if (cursor == 0) { + break; + } + } + } else { + while(true) { + cursor = _redis->scan(cursor, "network-nodes-online:"+controllerId+":*", INT_MAX, std::inserter(keys, keys.begin())); + if (cursor == 0) { + break; + } + } + } + + tx.zremrangebyscore("nodes-online:{"+controllerId+"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); + + for(const auto &k : keys) { + tx.zremrangebyscore(k, sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); + } + + tx.exec(); +} + PGconn *PostgreSQL::getPgConn(OverrideMode m) { if (m == ALLOW_PGBOUNCER_OVERRIDE) { diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index 44347cd81..f61670132 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -70,6 +70,10 @@ private: void commitThread(); void onlineNotificationThread(); + void onlineNotification_Postgres(); + void onlineNotification_Redis(); + void _doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId, + std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline); enum OverrideMode { ALLOW_PGBOUNCER_OVERRIDE = 0, From 7a138f963cc8131b3d3e153f242fd1e89443f54b Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 18 May 2020 13:58:29 -0700 Subject: [PATCH 068/362] TIL: Creating a redis transaction without sending any commands throws an exception So let's not do that --- controller/PostgreSQL.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 05d2de7b1..28db4a867 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1540,15 +1540,15 @@ void PostgreSQL::onlineNotification_Redis() std::lock_guard l(_lastOnline_l); lastOnline.swap(_lastOnline); } - - if (_rc->clusterMode) { - auto tx = _cluster->redis(controllerId).transaction(true); - _doRedisUpdate(tx, controllerId, lastOnline); - } else { - auto tx = _redis->transaction(true); - _doRedisUpdate(tx, controllerId, lastOnline); - } - + if (!lastOnline.empty()) { + if (_rc->clusterMode) { + auto tx = _cluster->redis(controllerId).transaction(true); + _doRedisUpdate(tx, controllerId, lastOnline); + } else { + auto tx = _redis->transaction(true); + _doRedisUpdate(tx, controllerId, lastOnline); + } + } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } @@ -1557,6 +1557,7 @@ void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &control std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline) { + fprintf(stderr, "Redis Update\n"); nlohmann::json jtmp1, jtmp2; for (auto i=lastOnline.begin(); i != lastOnline.end(); ++i) { uint64_t nwid_i = i->first.first; From 0f17508cac061e92d00e997a5a69fc1b7ed49553 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 20 May 2020 11:38:04 -0700 Subject: [PATCH 069/362] error recovery in redis online notification If a redis cluster member fails over to the slave, we'll get an error from not specifying the key for the insert. Recover from that instead of crashing the controller --- controller/PostgreSQL.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 28db4a867..fb3867cac 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1540,15 +1540,19 @@ void PostgreSQL::onlineNotification_Redis() std::lock_guard l(_lastOnline_l); lastOnline.swap(_lastOnline); } - if (!lastOnline.empty()) { - if (_rc->clusterMode) { - auto tx = _cluster->redis(controllerId).transaction(true); - _doRedisUpdate(tx, controllerId, lastOnline); - } else { - auto tx = _redis->transaction(true); - _doRedisUpdate(tx, controllerId, lastOnline); + try { + if (!lastOnline.empty()) { + if (_rc->clusterMode) { + auto tx = _cluster->redis(controllerId).transaction(true); + _doRedisUpdate(tx, controllerId, lastOnline); + } else { + auto tx = _redis->transaction(true); + _doRedisUpdate(tx, controllerId, lastOnline); + } } - } + } catch (sw::redis::Error &e) { + fprintf(stderr, "Error in online notification thread (redis): %s\n", e.what()); + } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } From 8b8399efbca76c7ec888276901579608562525a2 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 20 May 2020 11:42:51 -0700 Subject: [PATCH 070/362] Error recovery for network & member stream watchers --- controller/PostgreSQL.cpp | 154 ++++++++++++++++++++------------------ 1 file changed, 81 insertions(+), 73 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index fb3867cac..356166fb7 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -735,50 +735,54 @@ void PostgreSQL::_membersWatcher_Redis() { std::string key = "member-stream:{" + std::string(_myAddress.toString(buf)) + "}"; while (_run == 1) { - json tmp; - std::unordered_map result; - if (_rc->clusterMode) { - _cluster->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); - } else { - _redis->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); - } - if (!result.empty()) { - for (auto element : result) { -#ifdef ZT_TRACE - fprintf(stdout, "Received notification from: %s\n", element.first.c_str()); -#endif - for (auto rec : element.second) { - std::string id = rec.first; - auto attrs = rec.second; -#ifdef ZT_TRACE - fprintf(stdout, "Record ID: %s\n", id.c_str()); - fprintf(stdout, "attrs len: %lu\n", attrs.size()); -#endif - for (auto a : attrs) { -#ifdef ZT_TRACE - fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str()); -#endif - try { - tmp = json::parse(a.second); - json &ov = tmp["old_val"]; - json &nv = tmp["new_val"]; - json oldConfig, newConfig; - if (ov.is_object()) oldConfig = ov; - if (nv.is_object()) newConfig = nv; - if (oldConfig.is_object()||newConfig.is_object()) { - _memberChanged(oldConfig,newConfig,(this->_ready >= 2)); + try { + json tmp; + std::unordered_map result; + if (_rc->clusterMode) { + _cluster->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); + } else { + _redis->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); + } + if (!result.empty()) { + for (auto element : result) { + #ifdef ZT_TRACE + fprintf(stdout, "Received notification from: %s\n", element.first.c_str()); + #endif + for (auto rec : element.second) { + std::string id = rec.first; + auto attrs = rec.second; + #ifdef ZT_TRACE + fprintf(stdout, "Record ID: %s\n", id.c_str()); + fprintf(stdout, "attrs len: %lu\n", attrs.size()); + #endif + for (auto a : attrs) { + #ifdef ZT_TRACE + fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str()); + #endif + try { + tmp = json::parse(a.second); + json &ov = tmp["old_val"]; + json &nv = tmp["new_val"]; + json oldConfig, newConfig; + if (ov.is_object()) oldConfig = ov; + if (nv.is_object()) newConfig = nv; + if (oldConfig.is_object()||newConfig.is_object()) { + _memberChanged(oldConfig,newConfig,(this->_ready >= 2)); + } + } catch (...) { + fprintf(stderr, "json parse error in networkWatcher_Redis\n"); } - } catch (...) { - fprintf(stderr, "json parse error in networkWatcher_Redis\n"); } - } - if (_rc->clusterMode) { - _cluster->xdel(key, id); - } else { - _redis->xdel(key, id); + if (_rc->clusterMode) { + _cluster->xdel(key, id); + } else { + _redis->xdel(key, id); + } } } } + } catch (sw::redis::Error &e) { + fprintf(stderr, "Error in Redis members watcher: %s\n", e.what()); } } fprintf(stderr, "membersWatcher ended\n"); @@ -856,51 +860,55 @@ void PostgreSQL::_networksWatcher_Redis() { std::string key = "network-stream:{" + std::string(_myAddress.toString(buf)) + "}"; while (_run == 1) { - json tmp; - std::unordered_map result; - if (_rc->clusterMode) { - _cluster->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); - } else { - _redis->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); - } - - if (!result.empty()) { - for (auto element : result) { + try { + json tmp; + std::unordered_map result; + if (_rc->clusterMode) { + _cluster->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); + } else { + _redis->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); + } + + if (!result.empty()) { + for (auto element : result) { #ifdef ZT_TRACE - fprintf(stdout, "Received notification from: %s\n", element.first.c_str()); + fprintf(stdout, "Received notification from: %s\n", element.first.c_str()); #endif - for (auto rec : element.second) { - std::string id = rec.first; - auto attrs = rec.second; + for (auto rec : element.second) { + std::string id = rec.first; + auto attrs = rec.second; #ifdef ZT_TRACE - fprintf(stdout, "Record ID: %s\n", id.c_str()); - fprintf(stdout, "attrs len: %lu\n", attrs.size()); + fprintf(stdout, "Record ID: %s\n", id.c_str()); + fprintf(stdout, "attrs len: %lu\n", attrs.size()); #endif - for (auto a : attrs) { + for (auto a : attrs) { #ifdef ZT_TRACE - fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str()); + fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str()); #endif - try { - tmp = json::parse(a.second); - json &ov = tmp["old_val"]; - json &nv = tmp["new_val"]; - json oldConfig, newConfig; - if (ov.is_object()) oldConfig = ov; - if (nv.is_object()) newConfig = nv; - if (oldConfig.is_object()||newConfig.is_object()) { - _networkChanged(oldConfig,newConfig,(this->_ready >= 2)); + try { + tmp = json::parse(a.second); + json &ov = tmp["old_val"]; + json &nv = tmp["new_val"]; + json oldConfig, newConfig; + if (ov.is_object()) oldConfig = ov; + if (nv.is_object()) newConfig = nv; + if (oldConfig.is_object()||newConfig.is_object()) { + _networkChanged(oldConfig,newConfig,(this->_ready >= 2)); + } + } catch (...) { + fprintf(stderr, "json parse error in networkWatcher_Redis\n"); } - } catch (...) { - fprintf(stderr, "json parse error in networkWatcher_Redis\n"); } - } - if (_rc->clusterMode) { - _cluster->xdel(key, id); - } else { - _redis->xdel(key, id); + if (_rc->clusterMode) { + _cluster->xdel(key, id); + } else { + _redis->xdel(key, id); + } } } } + } catch (sw::redis::Error &e) { + fprintf(stderr, "Error in Redis networks watcher: %s\n", e.what()); } } fprintf(stderr, "networksWatcher ended\n"); From 879ef58565d7758b0fecf9c2275c9848f26d1d00 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 20 May 2020 16:28:28 -0700 Subject: [PATCH 071/362] Finalize Redis integration --- controller/PostgreSQL.cpp | 152 ++++++++++++++++++++++++++++++++------ 1 file changed, 131 insertions(+), 21 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 356166fb7..0c81ead91 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -113,11 +113,8 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, R fprintf(stderr, "Central database schema version too low. This controller version requires a minimum schema version of %d. Please upgrade your Central instance", DB_MINIMUM_VERSION); exit(1); } - PQclear(res); res = NULL; - PQfinish(conn); - conn = NULL; if (_rc != NULL) { sw::redis::ConnectionOptions opts; @@ -137,6 +134,16 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, R } _readyLock.lock(); + + fprintf(stderr, "[%s] NOTICE: %.10llx controller PostgreSQL waiting for initial data download..." ZT_EOL_S, ::_timestr(), (unsigned long long)_myAddress.toInt()); + _waitNoticePrinted = true; + + initializeNetworks(conn); + initializeMembers(conn); + + PQfinish(conn); + conn = NULL; + _heartbeatThread = std::thread(&PostgreSQL::heartbeat, this); _membersDbWatcher = std::thread(&PostgreSQL::membersDbWatcher, this); _networksDbWatcher = std::thread(&PostgreSQL::networksDbWatcher, this); @@ -165,10 +172,6 @@ PostgreSQL::~PostgreSQL() bool PostgreSQL::waitForReady() { while (_ready < 2) { - if (!_waitNoticePrinted) { - _waitNoticePrinted = true; - fprintf(stderr, "[%s] NOTICE: %.10llx controller PostgreSQL waiting for initial data download..." ZT_EOL_S, ::_timestr(), (unsigned long long)_myAddress.toInt()); - } _readyLock.lock(); _readyLock.unlock(); } @@ -236,6 +239,7 @@ void PostgreSQL::eraseNetwork(const uint64_t networkId) void PostgreSQL::eraseMember(const uint64_t networkId, const uint64_t memberId) { char tmp2[24]; + waitForReady(); std::pair tmp, nw; Utils::hex(networkId, tmp2); tmp.first["nwid"] = tmp2; @@ -265,11 +269,28 @@ void PostgreSQL::initializeNetworks(PGconn *conn) fprintf(stderr, "Bad Database Connection: %s", PQerrorMessage(conn)); exit(1); } + + std::string setKey = "networks:{" + std::string(_myAddressStr.c_str()) + "}"; + if (_rc != NULL) { + try { + if (_rc->clusterMode) { + _cluster->del(setKey); + } else { + _redis->del(setKey); + } + } catch (sw::redis::Error &e) { + // del can throw an error if the key doesn't exist + // swallow it and move along + } + } + const char *params[1] = { _myAddressStr.c_str() }; + fprintf(stderr, "Initializing Networks...\n"); + PGresult *res = PQexecParams(conn, "SELECT id, EXTRACT(EPOCH FROM creation_time AT TIME ZONE 'UTC')*1000, capabilities, " "enable_broadcast, EXTRACT(EPOCH FROM last_modified AT TIME ZONE 'UTC')*1000, mtu, multicast_limit, name, private, remote_trace_level, " "remote_trace_target, revision, rules, tags, v4_assign_mode, v6_assign_mode FROM ztc_network " @@ -295,9 +316,18 @@ void PostgreSQL::initializeNetworks(PGconn *conn) const char *nwidparam[1] = { PQgetvalue(res, i, 0) }; + std::string nwid = PQgetvalue(res, i, 0); + + if (_rc != NULL) { + if (_rc->clusterMode) { + _cluster->sadd(setKey, nwid); + } else { + _redis->sadd(setKey, nwid); + } + } - config["id"] = PQgetvalue(res, i, 0); - config["nwid"] = PQgetvalue(res, i, 0); + config["id"] = nwid; + config["nwid"] = nwid; try { config["creationTime"] = std::stoull(PQgetvalue(res, i, 1)); } catch (std::exception &e) { @@ -416,8 +446,11 @@ void PostgreSQL::initializeNetworks(PGconn *conn) } _readyLock.unlock(); } + } catch (sw::redis::Error &e) { + fprintf(stderr, "ERROR: Error initializing networks in Redis: %s\n", e.what()); + exit(-1); } catch (std::exception &e) { - fprintf(stderr, "ERROR: Error initializing networks: %s", e.what()); + fprintf(stderr, "ERROR: Error initializing networks: %s\n", e.what()); exit(-1); } } @@ -429,11 +462,32 @@ void PostgreSQL::initializeMembers(PGconn *conn) fprintf(stderr, "Bad Database Connection: %s", PQerrorMessage(conn)); exit(1); } - + std::string setKeyBase = "network-nodes-all:{" + std::string(_myAddressStr.c_str()) + "}:"; + if (_rc != NULL) { + std::lock_guard l(_networks_l); + for ( auto it : _networks) { + uint64_t nwid_i = it.first; + char nwidTmp[64] = {0}; + OSUtils::ztsnprintf(nwidTmp, sizeof(nwidTmp), "%.16llx", nwid_i); + std::string nwid(nwidTmp); + std::string key = setKeyBase + nwid; + if (_rc->clusterMode) { + try { + _cluster->del(key); + } catch (...) {} + } else { + try { + _redis->del(key); + } catch (...) {} + } + } + } + const char *params[1] = { _myAddressStr.c_str() }; + fprintf(stderr, "Initializing Members...\n"); PGresult *res = PQexecParams(conn, "SELECT m.id, m.network_id, m.active_bridge, m.authorized, m.capabilities, EXTRACT(EPOCH FROM m.creation_time AT TIME ZONE 'UTC')*1000, m.identity, " " EXTRACT(EPOCH FROM m.last_authorized_time AT TIME ZONE 'UTC')*1000, " @@ -464,6 +518,15 @@ void PostgreSQL::initializeMembers(PGconn *conn) std::string memberId(PQgetvalue(res, i, 0)); std::string networkId(PQgetvalue(res, i, 1)); + + if (_rc != NULL) { + if (_rc->clusterMode) { + _cluster->sadd(setKeyBase + networkId, memberId); + } else { + _redis->sadd(setKeyBase + networkId, memberId); + } + } + std::string ctime = PQgetvalue(res, i, 5); config["id"] = memberId; config["nwid"] = networkId; @@ -564,6 +627,14 @@ void PostgreSQL::initializeMembers(PGconn *conn) config["ipAssignments"].push_back(ipaddr); } + if (_rc != NULL) { + if (_rc->clusterMode) { + _cluster->sadd(setKeyBase + networkId, memberId); + } else { + _redis->sadd(setKeyBase + networkId, memberId); + } + } + _memberChanged(empty, config, false); } @@ -575,6 +646,8 @@ void PostgreSQL::initializeMembers(PGconn *conn) } _readyLock.unlock(); } + } catch (sw::redis::Error &e) { + fprintf(stderr, "ERROR: Error initializing members (redis): %s\n", e.what()); } catch (std::exception &e) { fprintf(stderr, "ERROR: Error initializing members: %s\n", e.what()); exit(-1); @@ -670,8 +743,6 @@ void PostgreSQL::membersDbWatcher() exit(1); } - initializeMembers(conn); - if (_rc) { PQfinish(conn); conn = NULL; @@ -797,8 +868,6 @@ void PostgreSQL::networksDbWatcher() exit(1); } - initializeNetworks(conn); - if (_rc) { PQfinish(conn); conn = NULL; @@ -1344,6 +1413,20 @@ void PostgreSQL::commitThread() } catch (std::exception &e) { fprintf(stderr, "ERROR: Error updating member: %s\n", e.what()); } + if (_rc != NULL) { + try { + std::string id = (*config)["id"]; + std::string controllerId = _myAddressStr.c_str(); + std::string key = "networks:{" + controllerId + "}"; + if (_rc->clusterMode) { + _cluster->sadd(key, id); + } else { + _redis->sadd(key, id); + } + } catch (sw::redis::Error &e) { + fprintf(stderr, "ERROR: Error adding network to Redis: %s\n", e.what()); + } + } } else if (objtype == "_delete_network") { try { std::string networkId = (*config)["nwid"]; @@ -1367,6 +1450,20 @@ void PostgreSQL::commitThread() } catch (std::exception &e) { fprintf(stderr, "ERROR: Error deleting network: %s\n", e.what()); } + if (_rc != NULL) { + try { + std::string id = (*config)["id"]; + std::string controllerId = _myAddressStr.c_str(); + std::string key = "networks:{" + controllerId + "}"; + if (_rc->clusterMode) { + _cluster->srem(key, id); + } else { + _redis->srem(key, id); + } + } catch (sw::redis::Error &e) { + fprintf(stderr, "ERROR: Error adding network to Redis: %s\n", e.what()); + } + } } else if (objtype == "_delete_member") { try { std::string memberId = (*config)["id"]; @@ -1394,6 +1491,21 @@ void PostgreSQL::commitThread() } catch (std::exception &e) { fprintf(stderr, "ERROR: Error deleting member: %s\n", e.what()); } + if (_rc != NULL) { + try { + std::string memberId = (*config)["id"]; + std::string networkId = (*config)["nwid"]; + std::string controllerId = _myAddressStr.c_str(); + std::string key = "network-nodes-all:{" + controllerId + "}:" + networkId; + if (_rc->clusterMode) { + _cluster->srem(key, memberId); + } else { + _redis->srem(key, memberId); + } + } catch (sw::redis::Error &e) { + fprintf(stderr, "ERROR: Error deleting member from Redis: %s\n", e.what()); + } + } } else { fprintf(stderr, "ERROR: unknown objtype"); } @@ -1414,6 +1526,8 @@ void PostgreSQL::commitThread() void PostgreSQL::onlineNotificationThread() { + waitForReady(); + if (_rc != NULL) { onlineNotification_Redis(); } else { @@ -1569,7 +1683,6 @@ void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &control std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline) { - fprintf(stderr, "Redis Update\n"); nlohmann::json jtmp1, jtmp2; for (auto i=lastOnline.begin(); i != lastOnline.end(); ++i) { uint64_t nwid_i = i->first.first; @@ -1581,12 +1694,9 @@ void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &control OSUtils::ztsnprintf(memTmp,sizeof(memTmp), "%.10llx", memberid_i); if (!get(nwid_i, jtmp1, memberid_i, jtmp2)){ + fprintf(stderr, "network or member doesn't exist\n"); continue; // skip non existent members/networks } - auto found = _networks.find(nwid_i); - if (found == _networks.end()) { - continue; // skip members trying to join non-existant networks - } std::string networkId(nwidTmp); std::string memberId(memTmp); @@ -1603,7 +1713,7 @@ void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &control tx.zadd("nodes-online:{"+controllerId+"}", memberId, ts) .zadd("network-nodes-online:{"+controllerId+"}:"+networkId, memberId, ts) .sadd("network-nodes-all:{"+controllerId+"}:"+networkId, memberId) - .hmset("network:{"+controllerId+"}:"+networkId+":"+memberId, record.begin(), record.end()); + .hmset("member:{"+controllerId+"}:"+networkId+":"+memberId, record.begin(), record.end()); } tx.exec(); From d24c8d858ca9f542f304618e728e090c31b5b5ad Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 20 May 2020 16:54:18 -0700 Subject: [PATCH 072/362] include climits for Linux --- controller/PostgreSQL.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 0c81ead91..1e59365ad 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -22,6 +22,7 @@ #include #include +#include using json = nlohmann::json; From 08cb72bdba5bb20b5244af09445d71b0b12e727b Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 21 May 2020 09:33:03 -0700 Subject: [PATCH 073/362] Temp object was being destroyed before connection was used --- controller/PostgreSQL.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 1e59365ad..a9915ee11 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1666,7 +1666,8 @@ void PostgreSQL::onlineNotification_Redis() try { if (!lastOnline.empty()) { if (_rc->clusterMode) { - auto tx = _cluster->redis(controllerId).transaction(true); + auto redis = _cluster->redis(controllerId); + auto tx = redis.transaction(true); _doRedisUpdate(tx, controllerId, lastOnline); } else { auto tx = _redis->transaction(true); From c2409ad6c993bf3222bc115e348fba3078398c7f Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 21 May 2020 09:49:41 -0700 Subject: [PATCH 074/362] fix connection to redis cluster in online notification thread --- controller/PostgreSQL.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index a9915ee11..c3cfe7f04 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1666,8 +1666,7 @@ void PostgreSQL::onlineNotification_Redis() try { if (!lastOnline.empty()) { if (_rc->clusterMode) { - auto redis = _cluster->redis(controllerId); - auto tx = redis.transaction(true); + auto tx = _cluster->transaction(controllerId, true); _doRedisUpdate(tx, controllerId, lastOnline); } else { auto tx = _redis->transaction(true); From 13929aee6f48cb201d4575a0095cc03704255ebc Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 21 May 2020 09:49:55 -0700 Subject: [PATCH 075/362] reduce log chattiness --- controller/PostgreSQL.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index c3cfe7f04..4de01172e 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1674,7 +1674,9 @@ void PostgreSQL::onlineNotification_Redis() } } } catch (sw::redis::Error &e) { +#ifdef ZT_TRACE fprintf(stderr, "Error in online notification thread (redis): %s\n", e.what()); +#endif } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } @@ -1695,7 +1697,6 @@ void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &control OSUtils::ztsnprintf(memTmp,sizeof(memTmp), "%.10llx", memberid_i); if (!get(nwid_i, jtmp1, memberid_i, jtmp2)){ - fprintf(stderr, "network or member doesn't exist\n"); continue; // skip non existent members/networks } From fb0e8aebdb1ed168c8ff2dd7e30d110db38d6f8f Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Fri, 22 May 2020 10:07:39 -0700 Subject: [PATCH 076/362] keep list of active networks in redis --- controller/PostgreSQL.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 4de01172e..6bc2b3a4e 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1714,6 +1714,7 @@ void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &control }; tx.zadd("nodes-online:{"+controllerId+"}", memberId, ts) .zadd("network-nodes-online:{"+controllerId+"}:"+networkId, memberId, ts) + .zadd("active-networks:{"+controllerId+"}", networkId, ts) .sadd("network-nodes-all:{"+controllerId+"}:"+networkId, memberId) .hmset("member:{"+controllerId+"}:"+networkId+":"+memberId, record.begin(), record.end()); } @@ -1745,7 +1746,7 @@ void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &control } tx.zremrangebyscore("nodes-online:{"+controllerId+"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); - + tx.zremrangebyscore("active-networks:{"+controllerId}"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); for(const auto &k : keys) { tx.zremrangebyscore(k, sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); } From beedee4401a51327935ff15ddb3a3d4e2a608ee9 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Fri, 22 May 2020 11:07:12 -0700 Subject: [PATCH 077/362] fix typo --- controller/PostgreSQL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 6bc2b3a4e..7e7677ad4 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1746,7 +1746,7 @@ void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &control } tx.zremrangebyscore("nodes-online:{"+controllerId+"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); - tx.zremrangebyscore("active-networks:{"+controllerId}"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); + tx.zremrangebyscore("active-networks:{"+controllerId+"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); for(const auto &k : keys) { tx.zremrangebyscore(k, sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); } From 39da360725cd5ff4297c504569cf20521beefe74 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Fri, 22 May 2020 14:16:04 -0700 Subject: [PATCH 078/362] add online controller list in Redis --- controller/PostgreSQL.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 7e7677ad4..36d6da0a5 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -686,12 +686,13 @@ void PostgreSQL::heartbeat() PQfinish(conn); exit(6); } + int64_t ts = OSUtils::now(); if (conn) { std::string major = std::to_string(ZEROTIER_ONE_VERSION_MAJOR); std::string minor = std::to_string(ZEROTIER_ONE_VERSION_MINOR); std::string rev = std::to_string(ZEROTIER_ONE_VERSION_REVISION); std::string build = std::to_string(ZEROTIER_ONE_VERSION_BUILD); - std::string now = std::to_string(OSUtils::now()); + std::string now = std::to_string(ts); std::string host_port = std::to_string(_listenPort); std::string use_redis = (_rc != NULL) ? "true" : "false"; const char *values[10] = { @@ -726,6 +727,13 @@ void PostgreSQL::heartbeat() } PQclear(res); } + if (_rc != NULL) { + if (_rc->clusterMode) { + _cluster->zadd("controllers", controllerId, ts); + } else { + _redis->zadd("controllers", controllerId, ts); + } + } std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } From 7ed960297bb2719a361f13c673d2252e9ac52bdd Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 26 May 2020 17:57:09 -0700 Subject: [PATCH 079/362] Merge changes from dev into multipath --- controller/PostgreSQL.cpp | 136 +++++--------------------------------- node/NetworkConfig.cpp | 8 +-- node/NetworkConfig.hpp | 7 ++ 3 files changed, 28 insertions(+), 123 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 05d2de7b1..b8fd749a5 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -229,14 +229,12 @@ void PostgreSQL::eraseNetwork(const uint64_t networkId) tmp.first["objtype"] = "_delete_network"; tmp.second = true; _commitQueue.post(tmp); - nlohmann::json nullJson; - _networkChanged(tmp.first, nullJson, true); } void PostgreSQL::eraseMember(const uint64_t networkId, const uint64_t memberId) { char tmp2[24]; - std::pair tmp, nw; + std::pair tmp; Utils::hex(networkId, tmp2); tmp.first["nwid"] = tmp2; Utils::hex(memberId, tmp2); @@ -244,8 +242,6 @@ void PostgreSQL::eraseMember(const uint64_t networkId, const uint64_t memberId) tmp.first["objtype"] = "_delete_member"; tmp.second = true; _commitQueue.post(tmp); - nlohmann::json nullJson; - _memberChanged(tmp.first, nullJson, true); } void PostgreSQL::nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress &physicalAddress) @@ -634,7 +630,7 @@ void PostgreSQL::heartbeat() }; PGresult *res = PQexecParams(conn, - "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_redis) " + "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port,use_redis) " "VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10) " "ON CONFLICT (id) DO UPDATE SET cluster_host = EXCLUDED.cluster_host, last_alive = EXCLUDED.last_alive, " "public_identity = EXCLUDED.public_identity, v_major = EXCLUDED.v_major, v_minor = EXCLUDED.v_minor, " @@ -1405,15 +1401,6 @@ void PostgreSQL::commitThread() } void PostgreSQL::onlineNotificationThread() -{ - if (_rc != NULL) { - onlineNotification_Redis(); - } else { - onlineNotification_Postgres(); - } -} - -void PostgreSQL::onlineNotification_Postgres() { PGconn *conn = getPgConn(); if (PQstatus(conn) == CONNECTION_BAD) { @@ -1423,7 +1410,9 @@ void PostgreSQL::onlineNotification_Postgres() } _connected = 1; - nlohmann::json jtmp1, jtmp2; + //int64_t lastUpdatedNetworkStatus = 0; + std::unordered_map< std::pair,int64_t,_PairHasher > lastOnlineCumulative; + while (_run == 1) { if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "ERROR: Online Notification thread lost connection to Postgres."); @@ -1431,6 +1420,9 @@ void PostgreSQL::onlineNotification_Postgres() exit(5); } + // map used to send notifications to front end + std::unordered_map> updateMap; + std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; { std::lock_guard l(_lastOnline_l); @@ -1451,13 +1443,20 @@ void PostgreSQL::onlineNotification_Postgres() OSUtils::ztsnprintf(nwidTmp,sizeof(nwidTmp), "%.16llx", nwid_i); OSUtils::ztsnprintf(memTmp,sizeof(memTmp), "%.10llx", i->first.second); - if(!get(nwid_i, jtmp1, i->first.second, jtmp2)) { - continue; // skip non existent networks/members + auto found = _networks.find(nwid_i); + if (found == _networks.end()) { + continue; // skip members trying to join non-existant networks } std::string networkId(nwidTmp); std::string memberId(memTmp); + std::vector &members = updateMap[networkId]; + members.push_back(memberId); + + lastOnlineCumulative[i->first] = i->second.first; + + const char *qvals[2] = { networkId.c_str(), memberId.c_str() @@ -1527,107 +1526,6 @@ void PostgreSQL::onlineNotification_Postgres() } } -void PostgreSQL::onlineNotification_Redis() -{ - _connected = 1; - - char buf[11] = {0}; - std::string controllerId = std::string(_myAddress.toString(buf)); - - while (_run == 1) { - std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; - { - std::lock_guard l(_lastOnline_l); - lastOnline.swap(_lastOnline); - } - - if (_rc->clusterMode) { - auto tx = _cluster->redis(controllerId).transaction(true); - _doRedisUpdate(tx, controllerId, lastOnline); - } else { - auto tx = _redis->transaction(true); - _doRedisUpdate(tx, controllerId, lastOnline); - } - - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } -} - -void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId, - std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline) - -{ - nlohmann::json jtmp1, jtmp2; - for (auto i=lastOnline.begin(); i != lastOnline.end(); ++i) { - uint64_t nwid_i = i->first.first; - uint64_t memberid_i = i->first.second; - char nwidTmp[64]; - char memTmp[64]; - char ipTmp[64]; - OSUtils::ztsnprintf(nwidTmp,sizeof(nwidTmp), "%.16llx", nwid_i); - OSUtils::ztsnprintf(memTmp,sizeof(memTmp), "%.10llx", memberid_i); - - if (!get(nwid_i, jtmp1, memberid_i, jtmp2)){ - continue; // skip non existent members/networks - } - auto found = _networks.find(nwid_i); - if (found == _networks.end()) { - continue; // skip members trying to join non-existant networks - } - - std::string networkId(nwidTmp); - std::string memberId(memTmp); - - int64_t ts = i->second.first; - std::string ipAddr = i->second.second.toIpString(ipTmp); - std::string timestamp = std::to_string(ts); - - std::unordered_map record = { - {"id", memberId}, - {"address", ipAddr}, - {"last_updated", std::to_string(ts)} - }; - tx.zadd("nodes-online:{"+controllerId+"}", memberId, ts) - .zadd("network-nodes-online:{"+controllerId+"}:"+networkId, memberId, ts) - .sadd("network-nodes-all:{"+controllerId+"}:"+networkId, memberId) - .hmset("network:{"+controllerId+"}:"+networkId+":"+memberId, record.begin(), record.end()); - } - - tx.exec(); - - // expire records from all-nodes and network-nodes member list - uint64_t expireOld = OSUtils::now() - 300000; - - auto cursor = 0LL; - std::unordered_set keys; - // can't scan for keys in a transaction, so we need to fall back to _cluster or _redis - // to get all network-members keys - if(_rc->clusterMode) { - auto r = _cluster->redis(controllerId); - while(true) { - cursor = r.scan(cursor, "network-nodes-online:{"+controllerId+"}:*", INT_MAX, std::inserter(keys, keys.begin())); - if (cursor == 0) { - break; - } - } - } else { - while(true) { - cursor = _redis->scan(cursor, "network-nodes-online:"+controllerId+":*", INT_MAX, std::inserter(keys, keys.begin())); - if (cursor == 0) { - break; - } - } - } - - tx.zremrangebyscore("nodes-online:{"+controllerId+"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); - - for(const auto &k : keys) { - tx.zremrangebyscore(k, sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); - } - - tx.exec(); -} - PGconn *PostgreSQL::getPgConn(OverrideMode m) { if (m == ALLOW_PGBOUNCER_OVERRIDE) { diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index e45a111d2..97985c7af 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -22,7 +22,7 @@ namespace ZeroTier { bool NetworkConfig::toDictionary(Dictionary &d,bool includeLegacy) const { Buffer *tmp = new Buffer(); - char tmp2[128]; + char tmp2[128] = {0}; try { d.clear(); @@ -84,7 +84,7 @@ bool NetworkConfig::toDictionary(Dictionary &d,b if (((int)lastrt < 32)||(lastrt == ZT_NETWORK_RULE_MATCH_ETHERTYPE)) { if (ets.length() > 0) ets.push_back(','); - char tmp2[16]; + char tmp2[16] = {0}; ets.append(Utils::hex((uint16_t)et,tmp2)); } et = 0; @@ -104,7 +104,7 @@ bool NetworkConfig::toDictionary(Dictionary &d,b if ((this->specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) { if (ab.length() > 0) ab.push_back(','); - char tmp2[16]; + char tmp2[16] = {0}; ab.append(Address(this->specialists[i]).toString(tmp2)); } } @@ -220,7 +220,7 @@ bool NetworkConfig::fromDictionary(const Dictionary Date: Tue, 26 May 2020 17:57:37 -0700 Subject: [PATCH 080/362] Remove vestigial constructor, fix typos, clean up code --- node/Bond.cpp | 38 ++++++++++++++++++++++++-------------- node/Bond.hpp | 12 ++---------- node/BondController.hpp | 4 ++-- node/Path.hpp | 20 +++++++------------- service/OneService.cpp | 2 +- 5 files changed, 36 insertions(+), 40 deletions(-) diff --git a/node/Bond.cpp b/node/Bond.cpp index 2f283a696..9aef8f815 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -29,7 +29,8 @@ Bond::Bond(const RuntimeEnvironment *renv, int policy, const SharedPtr& pe _policyAlias = BondController::getPolicyStrByCode(policy); } -Bond::Bond(std::string& basePolicy, std::string& policyAlias, const SharedPtr& peer) : +Bond::Bond(const RuntimeEnvironment *renv, std::string& basePolicy, std::string& policyAlias, const SharedPtr& peer) : + RR(renv), _policyAlias(policyAlias), _peer(peer) { @@ -1518,20 +1519,23 @@ void Bond::setReasonableDefaults(int policy) _upDelay = 0; _allowFlowHashing=false; _bondMonitorInterval=0; - _allowPathNegotiation=false; _shouldCollectPathStatistics=false; - _lastPathNegotiationReceived=0; _lastBackgroundTaskCheck=0; + + // Path negotiation + _allowPathNegotiation=false; + _lastPathNegotiationReceived=0; _lastPathNegotiationCheck=0; + _pathNegotiationCutoffCount=0; + _localUtility=0; _lastFlowStatReset=0; _lastFlowExpirationCheck=0; - _localUtility=0; + _numBondedPaths=0; _rrPacketsSentOnCurrSlave=0; _rrIdx=0; - _lastPathNegotiationReceived=0; - _pathNegotiationCutoffCount=0; + _lastFlowRebalance=0; _totalBondUnderload = 0; @@ -1543,12 +1547,6 @@ void Bond::setReasonableDefaults(int policy) _lastFrame=0; - // TODO: Remove - _header=false; - _lastLogTS = 0; - _lastPrintTS = 0; - - /** @@ -1582,7 +1580,7 @@ void Bond::setReasonableDefaults(int policy) case ZT_BONDING_POLICY_BALANCE_RR: _failoverInterval = 5000; _allowFlowHashing = false; - _packetsPerSlave = 512; + _packetsPerSlave = 1024; _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; _qualityWeights[ZT_QOS_LAT_IDX] = 0.4f; _qualityWeights[ZT_QOS_LTM_IDX] = 0.0f; @@ -1653,11 +1651,23 @@ void Bond::setReasonableDefaults(int policy) _qosSendInterval = _bondMonitorInterval * 4; _qosCutoffCount = 0; _lastQoSRateCheck = 0; + _lastQualityEstimation=0; throughputMeasurementInterval = _ackSendInterval * 2; BondController::setMinReqPathMonitorInterval(_bondMonitorInterval); _defaultPathRefractoryPeriod = 8000; + + + + + // TODO: Remove + _header=false; + _lastLogTS = 0; + _lastPrintTS = 0; + + + fprintf(stderr, "TIMERS: strat=%d, fi= %d, bmi= %d, qos= %d, ack= %d, estimateInt= %d, refractory= %d, ud= %d, dd= %d\n", _slaveMonitorStrategy, _failoverInterval, @@ -1669,7 +1679,7 @@ void Bond::setReasonableDefaults(int policy) _upDelay, _downDelay); - _lastQualityEstimation=0; + } void Bond::setUserQualityWeights(float weights[], int len) diff --git a/node/Bond.hpp b/node/Bond.hpp index 89e4c905a..62195b18e 100644 --- a/node/Bond.hpp +++ b/node/Bond.hpp @@ -54,13 +54,6 @@ public: SharedPtr getSlave(const SharedPtr& path); - /** - * Constructor. For use only in first initialization in Node - * - * @param renv Runtime environment - */ - Bond(const RuntimeEnvironment *renv); - /** * Constructor. Creates a bond based off of ZT defaults * @@ -77,7 +70,7 @@ public: * @param policyAlias * @param peer */ - Bond(std::string& basePolicy, std::string& policyAlias, const SharedPtr& peer); + Bond(const RuntimeEnvironment *renv, std::string& basePolicy, std::string& policyAlias, const SharedPtr& peer); /** * Constructor. Creates a bond based off of a user-defined bond template @@ -89,8 +82,7 @@ public: Bond(const RuntimeEnvironment *renv, const Bond &original, const SharedPtr& peer); /** - * - * @return + * @return The human-readable name of the bonding policy */ std::string policyAlias() { return _policyAlias; } diff --git a/node/BondController.hpp b/node/BondController.hpp index acc70d2ff..95fbf81fc 100644 --- a/node/BondController.hpp +++ b/node/BondController.hpp @@ -36,7 +36,7 @@ public: BondController(const RuntimeEnvironment *renv); /** - * @return The minimum interval required to poll the active bonds to fulfill all active monitoring timing requirements. + * @return Whether this slave is permitted to become a member of a bond. */ bool slaveAllowed(std::string &policyAlias, SharedPtr slave); @@ -46,7 +46,7 @@ public: int minReqPathMonitorInterval() { return _minReqPathMonitorInterval; } /** - * @return The minimum interval required to poll the active bonds to fulfill all active monitoring timing requirements. + * @param minReqPathMonitorInterval The minimum interval required to poll the active bonds to fulfill all active monitoring timing requirements. */ static void setMinReqPathMonitorInterval(int minReqPathMonitorInterval) { _minReqPathMonitorInterval = minReqPathMonitorInterval; } diff --git a/node/Path.hpp b/node/Path.hpp index 22a932edf..1cbd588bc 100644 --- a/node/Path.hpp +++ b/node/Path.hpp @@ -127,8 +127,7 @@ public: _packetsReceivedSinceLastQoS(0), _bytesAckedSinceLastThroughputEstimation(0), _packetsIn(0), - _packetsOut(0), - _prevEligibility(false) + _packetsOut(0) {} Path(const int64_t localSocket,const InetAddress &addr) : @@ -177,8 +176,7 @@ public: _packetsReceivedSinceLastQoS(0), _bytesAckedSinceLastThroughputEstimation(0), _packetsIn(0), - _packetsOut(0), - _prevEligibility(false) + _packetsOut(0) {} /** @@ -187,10 +185,10 @@ public: * @param t Time of receive */ inline void received(const uint64_t t) { - _lastIn = t; - if (!_prevEligibility) { + if (!alive(t,_bonded)) { _lastAliveToggle = _lastIn; } + _lastIn = t; } /** @@ -506,7 +504,7 @@ private: uint64_t _lastQoSMeasurement; /** - * Last time that a the path's throughput was estimated. + * Last time that the path's throughput was estimated. */ uint64_t _lastThroughputEstimation; @@ -531,7 +529,7 @@ private: uint64_t _lastTrialBegin; /** - * Amount of time that this path is prevented from becoming a member of a bond. + * Amount of time that this path will be prevented from becoming a member of a bond. */ uint32_t _refractoryPeriod; @@ -576,7 +574,7 @@ private: bool _bonded; /** - * Whether this path was intentionally _negotiated by either peer. + * Whether this path was intentionally negotiated by either peer. */ bool _negotiated; @@ -684,10 +682,6 @@ private: */ int _packetsIn; int _packetsOut; - - // TODO: Remove - - bool _prevEligibility; }; } // namespace ZeroTier diff --git a/service/OneService.cpp b/service/OneService.cpp index 04734d7e2..ab8594eec 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -1596,7 +1596,7 @@ public: continue; } // New bond, used as a copy template for new instances - SharedPtr newTemplateBond = new Bond(basePolicyStr, customPolicyStr, SharedPtr()); + SharedPtr newTemplateBond = new Bond(NULL, basePolicyStr, customPolicyStr, SharedPtr()); // Acceptable ranges newTemplateBond->setMaxAcceptableLatency(OSUtils::jsonInt(customPolicy["maxAcceptableLatency"],-1)); newTemplateBond->setMaxAcceptableMeanLatency(OSUtils::jsonInt(customPolicy["maxAcceptableMeanLatency"],-1)); From a8f830aa9c216e70edee3d671a184ee3bb998cb6 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 26 May 2020 18:29:19 -0700 Subject: [PATCH 081/362] Add multipath documentation to service/ --- service/MULTIPATH.md | 250 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 service/MULTIPATH.md diff --git a/service/MULTIPATH.md b/service/MULTIPATH.md new file mode 100644 index 000000000..8a9e84603 --- /dev/null +++ b/service/MULTIPATH.md @@ -0,0 +1,250 @@ +### **2.1.5.** Link aggregation + +Link aggregation allows the simultaneous (or conditional) use of multiple physical links to enable increased throughput, load balancing, redundancy, and fault tolerance. There are a variety of standard policies available that can be used right out of the box with little to no configuration. These policies are directly inspired by [the policies offered by the Linux kernel](https://www.kernel.org/doc/Documentation/networking/bonding.txt). + +#### Standard Policies + +| Policy name | Fault tolerance | Min. failover (sec.) | Default Failover (sec.) | Balancing | Aggregation efficiency | Redundancy | Sequence Reordering | +|--------------------|:---------------------:|---------------------:|---------------------:|----------------------:|-----------------------:|-----------:|--------------------:| +| `none` | None | `60+` | `60+` | none | `none` |1 | No +| `active-backup` | Brief interruption | `0.25` | `10` | none | `low` |1 | Only during failover +| `broadcast` | Fully tolerant | `N/A` | `N/A` | none | `very low` |N | Often +| `balance-rr` | Self-healing | `0.25` | `10` | packet-based | `high` |1 | Often +| `balance-xor` | Self-healing | `0.25` | `10` | flow-based | `very high` |1 | Only during failover +| `balance-aware` | Self-healing | `0.25` | `10` | *adaptive* flow-based | `very high` |1 | Only during failover and re-balance + +A policy can be used easily without specifying any additional parameters: + +``` +{ + "settings": { + "defaultBondingPolicy": "active-backup" + } +} +``` + +#### Custom Policies + +To customize a bonding policy for your use-case simply specify a `basePolicy` and override chosen parameters. For example, to create a more aggressive `active-backup` policy with low monitoring overhead that will failover `0.250` seconds after it detects a link failure, one could do the following: + +``` +{ + "settings": + { + "defaultBondingPolicy": "aggressive-active-backup", + "policies": + { + "aggressive-active-backup": + { + "failoverInterval": 250, + "pathMonitorStrategy": "dynamic", + "basePolicy": "active-backup" + } + } + } +} +``` + +#### Specifying Slave interfaces + +Available system network interfaces are referred to as `slaves`. Different sets of slaves can be constructed for different bonding policies and used simultaneously. One can specify the links that ZeroTier should use in any given bonding policy simply by providing an array of slaves with names corresponding to interface names. If a user doesn't specify a set of interfaces to use, ZeroTier will assume every system interface is available for use. However, if the user **does** specify a set of interfaces, ZeroTier will only use what is specified. The same applies to failover rules, if none are specified, ZeroTier will failover to any operational slave. On the other hand, if the user does specify failover rules and there is ever a situation where a slave is available for usage but does not fit within the rules specified by the user, it will go unused. + +To specify that ZeroTier should only use `eth0` and `eth1` as primary slaves, and `eth2` as a backup spare and that it should prefer IPv4 over IPv6 except on `eth2` where only IPv6 is allowed: + +``` +{ + "settings": { + "defaultBondingPolicy": "aggressive-active-backup", + "policies": { + "aggressive-active-backup": { + "slaves": { + "eth0": { + "ipvPref": 46, + "failoverTo": "eth2", + "mode": "primary" + }, + "eth1": { + "ipvPref": 46, + "failoverTo": "eth2", + "mode": "primary" + }, + "eth2": { + "ipvPref": 6, + "mode": "spare" + } + } + } + } + } +} +``` + +Additional slave-specific parameters: + +``` +"slaves": +{ + "interfaceName": /* System-name of the network interface. */ + { + "failoverInterval": 0-65535, /* (optional) How quickly a path on this slave should failover after a detected failure. */ + "ipvPref": [0,4,6,46,64], /* (optional) IP version preference for detected paths on a slave. */ + "speed": 0-1000000, /* (optional) How fast this slave is (in arbitrary units). This is a useful way to manually allocate a bond. */ + "alloc": 0-255, /* (optional) A relative value representing a desired allocation. */ + "upDelay": 0-65535, /* (optional) How long after a path becomes alive before it is added to the bond. */ + "downDelay": 0-65535, /* (optional) How long after a path fails before it is removed from the bond. */ + "failoverTo": "spareInterfaceName", /* (optional) Which slave should be used next after a failure of this slave. */ + "enabled": true|false, /* (optional) Whether any paths on this slave are allowed to be used this bond. */ + "mode": "primary"|"spare" /* (optional) Whether this slave is used by default or only after failover events. */ + } +} +``` + +#### Peer-specific Bonds + +It is possible to direct ZeroTier to form a certain type of bond with specific peers of your choice. For instance, if one were to want `active-backup` by default but for certain peers to be bonded with a custom load-balanced bond such as `my-custom-balance-aware` one could do the following: + +``` +{ + "settings": + { + "defaultBondingPolicy": "active-backup", + "policies": + { + "my-custom-balance-aware": + { + "failoverInterval": 2000, + "monitorStrategy": "dynamic", + "basePolicy": "balance-aware" + } + }, + "peerSpecificBonds": + { + "f6203a2db3":"my-custom-balance-aware", + "45b0301da2":"my-custom-balance-aware", + "a92cb526fa":"my-custom-balance-aware" + } + } +} +``` + +#### Active Backup (`active-backup`) + +Traffic is sent only on (one) path at any given time. A different path becomes active if the current path fails. This mode provides fault tolerance with a nearly immediate fail-over. This mode **does not** increase total throughput. + + - `mode`: `primary, spare` Slave option which specifies which slave is the primary device. The specified device is intended to always be the active slave while it is available. There are exceptions to this behavior when using different `slaveSelectMethod` modes. There can only be one `primary` slave in this bonding policy. + + - `slaveSelectMethod`: Specifies the selection policy for the active slave during failure and/or recovery events. This is similar to the Linux Kernel's `primary_reselect` option but with a minor extension: + - `optimize`: **(default if user provides no failover guidance)** The primary slave can change periodically if a superior path is detected. + - `always`: **(default when slaves are explicitly specified)**: Primary slave regains status as active slave whenever it comes back up. + - `better`: Primary slave regains status as active slave when it comes back up and (if) it is better than the currently-active slave. + - `failure`: Primary slave regains status as active slave only if the currently-active slave fails. + +``` +{ + "settings": + { + "defaultBondingPolicy": "active-backup", + "active-backup": + { + "slaveSelectMethod": "always", + "slaves": + { + "eth0": { "failoverTo": "eth1", "mode": "primary" }, + "eth1": { "mode": "spare" }, + "eth2": { "mode": "spare" }, + "eth3": { "mode": "spare" } + } + } + } +} +``` + +#### Broadcast (`broadcast`) + +Traffic is sent on (all) available paths simultaneously. This mode provides fault tolerance and effectively immediate failover due to transmission redundancy. This mode is a poor utilization of throughput resources and will **not** increase throughput but can prevent packet loss during a link failure. The only option available is `dedup` which will de-duplicate all packets on the receiving end if set to `true`. + +#### Balance Round Robin (`balance-rr`) + +Traffic is striped across multiple paths. Offers partial fault tolerance immediately, full fault tolerance eventually. This policy is unaware of protocols and is primarily intended for use with protocols that are not sensitive to reordering delays. The only option available for this policy is `packetsPerSlave` which specifies the number of packets to transmit via a path before moving to the next in the RR sequence. When set to `0` a path is chosen at random for each outgoing packet. The default value is `8`, low values can begin to add overhead to packet processing. + +#### Balance XOR (`balance-xor`, similar to the Linux kernel's [balance-xor](https://www.kernel.org/doc/Documentation/networking/bonding.txt) with `xmit_hash_policy=layer3+4`) + +Traffic is categorized into *flows* based on *source port*, *destination port*, and *protocol type* these flows are then hashed onto available slaves. Each flow will persist on its assigned slave interface for its entire life-cycle. Traffic that does not have an assigned port (such as ICMP pings) will be randomly distributed across slaves. The hash function is simply: `src_port ^ dst_port ^ proto`. + +#### Balance Aware (`balance-aware`, similar to Linux kernel's [`balance-*lb`](https://www.kernel.org/doc/Documentation/networking/bonding.txt) modes) + +Traffic is dynamically allocated and balanced across multiple slaves simultaneously according to the target allocation. Options allow for *packet* or *flow-based* processing, and active-flow reassignment. Flows mediated over a recently failed slaves will be reassigned in a manner that respects the target allocation of the bond. An optional `balancePolicy` can be specified with the following effects: `flow-dynamic` (default) will hash flows onto slaves according to target allocation and may perform periodic re-assignments in order to preserve balance. `flow-static`, will hash flows onto slaves according to target allocation but will not re-assign flows unless a failure occurs or the slave is no longer operating within acceptable parameters. And lastly `packet` which simply load balances packets across slaves according to target allocation but with no concern for sequence reordering. + +``` +{ + "settings": + { + "defaultBondingPolicy": "balance-aware", + "balance-aware": { + "balancePolicy": "flow-dynamic"|"flow-static"|"packet" + } + } +} +``` + +#### Link Quality + +ZeroTier measures various properties of a link (such as latency, throughput, jitter, packet loss ratio, etc) in order to arrive at a quality estimate. This estimate is used by bonding policies to make allocation and failover decisions: + +| Policy name | Role | +|:---------------|:-----| +|`active-backup` | Determines the order of the failover queue. And if `activeReselect=optimize` whether a new active slave is selected. | +|`broadcast` | Does not use quality measurements. | +|`balance-rr` | May trigger removal of slave from bond. | +|`balance-xor` | May trigger removal of slave from bond. | +|`balance-aware` | Informs flow assignments and (re-)assignments. May trigger removal of slave from bond. | + +A slave's eligibility for being included in a bond is dependent on more than perceived quality. If a path on a slave begins to exhibit disruptive behavior such as extremely high packet loss, corruption, or periodic inability to process traffic it will be removed from the bond, its traffic will be appropriately reallocated and it will be punished. Punishments gradually fade and a slave can be readmitted to the bond over time. However, punishments increase exponentially if applied more than once within a given window of time. + +#### Asymmetric Links + +In cases where it is necessary to bond physical links that vary radically in terms of cost, throughput, latency, and or reliability, there are a couple of ways to automatically (or manually) allocate traffic among them. Traffic distribution and balancing can be either `packet` or `flow` based. Where packet-based is suitable for protocols not susceptible to reordering penalties and flow-based is suitable for protocols such as TCP where it is desirable to keep a conversation on a single link unless we can't avoid having to re-assign it. Additionally, a *target allocation* of traffic used by the bonding policy can be derived/specified in the following ways: + + - **Automatically**: This is the easiest and requires no user configuration. The bonding layer measures and senses the link properties and determines a target allocation based on perceived quality and capacity. Weaker, less reliable links will have less traffic allocated to them and stronger, more reliable links will have more traffic allocated to them. Optionally, the user can specify a set of weights (totaling `1.0`) to inform the bonding layer how important certain link properties are. For instance, one may primarily be concerned with latency and jitter but not total throughput: + +``` +"balance-aware": { + "quality": { + "lat": 0.3, /* Moving average of latency in milliseconds */ + "ltm": 0.2, /* Maximum observed latency in milliseconds */ + "pdv": 0.3, /* Packet delay variance in milliseconds. Similar to jitter */ + "plr": 0.1, /* Packet loss ratio */ + "per": 0.1, /* Packet error ratio */ + "thr": 0.0, /* Mean throughput */ + "thm": 0.0, /* Maximum observed throughput */ + "thv": 0.0, /* Variance of throughput */ + "avl": 0.0, /* Availability */ + } +} +``` +In the absence of user guidance ZeroTier will attempt to form an understanding of each link's speed and capacity but this value can be inaccurate if the links are not routinely saturated. Therefore we provide a way to explicitly signal the capacity of each link in terms of arbitrary but relative values: + +``` +"slaves": { + "eth0": { "speed": 10000 }, + "eth1": { "speed": 1000 }, + "eth2": { "speed": 100 } +} +``` + +The user specifies allocation percentages (totaling `1.0`). In this case quality measurements will only be used to determine a slave's eligibility to be a member of a bond, now how much traffic it will carry: + +``` +"slaves": { + "eth0": { "alloc": 0.50 }, + "eth1": { "alloc": 0.25 }, + "eth2": { "alloc": 0.25 } +} +``` + +#### Performance and Overhead Considerations + + - Only packets with internal IDs divisible by `16` are included in measurements, this amounts to about `6.25%` of all traffic. + - `failoverInterval` specifies how quickly failover should occur during a link failure. In order to accomplish this a combination of active and passive measurement techniques are employed which may result in `VERB_HELLO` probes being sent every `failoverInterval / 4` time units. As a mitigation `monitorStrategy` may be set to `dynamic` so that probe frequency directly correlates with native application traffic. + + From 7bde004c7c9dc13004e5a2c12a5d8aeb42cd6cca Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 27 May 2020 20:41:47 -0700 Subject: [PATCH 082/362] Replace scan of Redis with iteration of _networks map --- controller/PostgreSQL.cpp | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 36d6da0a5..fd52f4615 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1732,33 +1732,18 @@ void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &control // expire records from all-nodes and network-nodes member list uint64_t expireOld = OSUtils::now() - 300000; - auto cursor = 0LL; - std::unordered_set keys; - // can't scan for keys in a transaction, so we need to fall back to _cluster or _redis - // to get all network-members keys - if(_rc->clusterMode) { - auto r = _cluster->redis(controllerId); - while(true) { - cursor = r.scan(cursor, "network-nodes-online:{"+controllerId+"}:*", INT_MAX, std::inserter(keys, keys.begin())); - if (cursor == 0) { - break; - } - } - } else { - while(true) { - cursor = _redis->scan(cursor, "network-nodes-online:"+controllerId+":*", INT_MAX, std::inserter(keys, keys.begin())); - if (cursor == 0) { - break; - } - } - } - tx.zremrangebyscore("nodes-online:{"+controllerId+"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); tx.zremrangebyscore("active-networks:{"+controllerId+"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); - for(const auto &k : keys) { - tx.zremrangebyscore(k, sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); + { + std::lock_guard l(_networks_l); + for (const auto &it : _networks) { + uint64_t nwid_i = it.first; + char nwidTmp[64]; + OSUtils::ztsnprintf(nwidTmp,sizeof(nwidTmp), "%.16llx", nwid_i); + tx.zremrangebyscore("network-nodes-online:{"+controllerId+"}:"+nwidTmp, + sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); + } } - tx.exec(); } From 2f0f0e4f53071dd7b48d4d348b839664daa1fbf0 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 26 May 2020 18:54:27 -0700 Subject: [PATCH 083/362] redis init optimization --- controller/PostgreSQL.cpp | 81 +++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index fd52f4615..4e13040fe 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -271,7 +271,7 @@ void PostgreSQL::initializeNetworks(PGconn *conn) exit(1); } - std::string setKey = "networks:{" + std::string(_myAddressStr.c_str()) + "}"; + std::string setKey = "networks:{" + _myAddressStr + "}"; if (_rc != NULL) { try { @@ -286,6 +286,8 @@ void PostgreSQL::initializeNetworks(PGconn *conn) } } + std::unordered_set networkSet; + const char *params[1] = { _myAddressStr.c_str() }; @@ -319,13 +321,7 @@ void PostgreSQL::initializeNetworks(PGconn *conn) }; std::string nwid = PQgetvalue(res, i, 0); - if (_rc != NULL) { - if (_rc->clusterMode) { - _cluster->sadd(setKey, nwid); - } else { - _redis->sadd(setKey, nwid); - } - } + networkSet.insert(nwid); config["id"] = nwid; config["nwid"] = nwid; @@ -441,6 +437,16 @@ void PostgreSQL::initializeNetworks(PGconn *conn) PQclear(res); + if (_rc && _rc->clusterMode) { + auto tx = _cluster->transaction(_myAddressStr, true); + tx.sadd(setKey, networkSet.begin(), networkSet.end()); + tx.exec(); + } else if (_rc && !_rc->clusterMode) { + auto tx = _redis->transaction(true); + tx.sadd(setKey, networkSet.begin(), networkSet.end()); + tx.exec(); + } + if (++this->_ready == 2) { if (_waitNoticePrinted) { fprintf(stderr,"[%s] NOTICE: %.10llx controller PostgreSQL data download complete." ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt()); @@ -463,30 +469,40 @@ void PostgreSQL::initializeMembers(PGconn *conn) fprintf(stderr, "Bad Database Connection: %s", PQerrorMessage(conn)); exit(1); } - std::string setKeyBase = "network-nodes-all:{" + std::string(_myAddressStr.c_str()) + "}:"; + std::string setKeyBase = "network-nodes-all:{" + _myAddressStr + "}:"; + if (_rc != NULL) { std::lock_guard l(_networks_l); + std::unordered_set deletes; for ( auto it : _networks) { uint64_t nwid_i = it.first; char nwidTmp[64] = {0}; OSUtils::ztsnprintf(nwidTmp, sizeof(nwidTmp), "%.16llx", nwid_i); std::string nwid(nwidTmp); std::string key = setKeyBase + nwid; - if (_rc->clusterMode) { - try { - _cluster->del(key); - } catch (...) {} - } else { - try { - _redis->del(key); - } catch (...) {} + deletes.insert(key); + } + + if (_rc->clusterMode) { + auto tx = _cluster->transaction(_myAddressStr, true); + for (std::string k : deletes) { + tx.del(k); } + tx.exec(); + } else { + auto tx = _redis->transaction(true); + for (std::string k : deletes) { + tx.del(k); + } + tx.exec(); } } const char *params[1] = { _myAddressStr.c_str() }; + + std::unordered_map networkMembers; fprintf(stderr, "Initializing Members...\n"); PGresult *res = PQexecParams(conn, @@ -520,13 +536,7 @@ void PostgreSQL::initializeMembers(PGconn *conn) std::string memberId(PQgetvalue(res, i, 0)); std::string networkId(PQgetvalue(res, i, 1)); - if (_rc != NULL) { - if (_rc->clusterMode) { - _cluster->sadd(setKeyBase + networkId, memberId); - } else { - _redis->sadd(setKeyBase + networkId, memberId); - } - } + networkMembers.insert(std::pair(setKeyBase+networkId, memberId)); std::string ctime = PQgetvalue(res, i, 5); config["id"] = memberId; @@ -628,19 +638,26 @@ void PostgreSQL::initializeMembers(PGconn *conn) config["ipAssignments"].push_back(ipaddr); } - if (_rc != NULL) { - if (_rc->clusterMode) { - _cluster->sadd(setKeyBase + networkId, memberId); - } else { - _redis->sadd(setKeyBase + networkId, memberId); - } - } - _memberChanged(empty, config, false); } PQclear(res); + if (_rc != NULL) { + if (_rc->clusterMode) { + auto tx = _cluster->transaction(_myAddressStr, true); + for (auto it : networkMembers) { + tx.sadd(it.first, it.second); + } + tx.exec(); + } else { + auto tx = _redis->transaction(true); + for (auto it : networkMembers) { + tx.sadd(it.first, it.second); + } + tx.exec(); + } + } if (++this->_ready == 2) { if (_waitNoticePrinted) { fprintf(stderr,"[%s] NOTICE: %.10llx controller PostgreSQL data download complete." ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt()); From 9794e31a64b5bd3f3e9568b9f017dfe05eb41a26 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 26 May 2020 19:00:55 -0700 Subject: [PATCH 084/362] Temporary online notification thread hack Updates both Redis and Postgres --- controller/PostgreSQL.cpp | 82 ++++++++++++++++++++++++++++----------- controller/PostgreSQL.hpp | 2 +- 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 4e13040fe..57e09ed6a 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1554,15 +1554,6 @@ void PostgreSQL::onlineNotificationThread() { waitForReady(); - if (_rc != NULL) { - onlineNotification_Redis(); - } else { - onlineNotification_Postgres(); - } -} - -void PostgreSQL::onlineNotification_Postgres() -{ PGconn *conn = getPgConn(); if (PQstatus(conn) == CONNECTION_BAD) { fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn)); @@ -1571,19 +1562,64 @@ void PostgreSQL::onlineNotification_Postgres() } _connected = 1; - nlohmann::json jtmp1, jtmp2; while (_run == 1) { + std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; + { + std::lock_guard l(_lastOnline_l); + lastOnline.swap(_lastOnline); + } + + onlineNotification_Postgres(conn, lastOnline); + + try { + if (!lastOnline.empty()) { + if (_rc->clusterMode) { + auto tx = _cluster->transaction(_myAddressStr, true); + _doRedisUpdate(tx, _myAddressStr, lastOnline); + } else { + auto tx = _redis->transaction(true); + _doRedisUpdate(tx, _myAddressStr, lastOnline); + } + } + } catch (sw::redis::Error &e) { +#ifdef ZT_TRACE + fprintf(stderr, "Error in online notification thread (redis): %s\n", e.what()); +#endif + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + fprintf(stderr, "%s: Fell out of run loop in onlineNotificationThread\n", _myAddressStr.c_str()); + PQfinish(conn); + if (_run == 1) { + fprintf(stderr, "ERROR: %s onlineNotificationThread should still be running! Exiting Controller.\n", _myAddressStr.c_str()); + exit(6); + } + + // if (_rc != NULL) { + // onlineNotification_Redis(); + // } else { + // onlineNotification_Postgres(); + // } +} + +void PostgreSQL::onlineNotification_Postgres(PGconn *conn, std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline) +{ + + + nlohmann::json jtmp1, jtmp2; + // while (_run == 1) { if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "ERROR: Online Notification thread lost connection to Postgres."); PQfinish(conn); exit(5); } - std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; - { - std::lock_guard l(_lastOnline_l); - lastOnline.swap(_lastOnline); - } + // std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; + // { + // std::lock_guard l(_lastOnline_l); + // lastOnline.swap(_lastOnline); + // } PGresult *res = NULL; @@ -1665,14 +1701,14 @@ void PostgreSQL::onlineNotification_Postgres() PQclear(res); } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - fprintf(stderr, "%s: Fell out of run loop in onlineNotificationThread\n", _myAddressStr.c_str()); - PQfinish(conn); - if (_run == 1) { - fprintf(stderr, "ERROR: %s onlineNotificationThread should still be running! Exiting Controller.\n", _myAddressStr.c_str()); - exit(6); - } + // std::this_thread::sleep_for(std::chrono::milliseconds(10)); + // } + // fprintf(stderr, "%s: Fell out of run loop in onlineNotificationThread\n", _myAddressStr.c_str()); + // PQfinish(conn); + // if (_run == 1) { + // fprintf(stderr, "ERROR: %s onlineNotificationThread should still be running! Exiting Controller.\n", _myAddressStr.c_str()); + // exit(6); + // } } void PostgreSQL::onlineNotification_Redis() diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index f61670132..061349e1e 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -70,7 +70,7 @@ private: void commitThread(); void onlineNotificationThread(); - void onlineNotification_Postgres(); + void onlineNotification_Postgres(PGconn *conn, std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline); void onlineNotification_Redis(); void _doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId, std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline); From ad7ae5a3724a2114f1fe9047097c7fc40a873a03 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 27 May 2020 20:43:20 -0700 Subject: [PATCH 085/362] list of all network-node pairs --- controller/PostgreSQL.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 57e09ed6a..31573705d 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1774,6 +1774,7 @@ void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &control {"last_updated", std::to_string(ts)} }; tx.zadd("nodes-online:{"+controllerId+"}", memberId, ts) + .zadd("nodes-online2:{"+controllerId+"}", networkId+"-"+memberId, ts) .zadd("network-nodes-online:{"+controllerId+"}:"+networkId, memberId, ts) .zadd("active-networks:{"+controllerId+"}", networkId, ts) .sadd("network-nodes-all:{"+controllerId+"}:"+networkId, memberId) @@ -1786,6 +1787,7 @@ void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &control uint64_t expireOld = OSUtils::now() - 300000; tx.zremrangebyscore("nodes-online:{"+controllerId+"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); + tx.zremrangebyscore("nodes-online2:{"+controllerId+"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); tx.zremrangebyscore("active-networks:{"+controllerId+"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); { std::lock_guard l(_networks_l); From 5692402d32adbe6fec5fb5bf31d4c7b84d516731 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 27 May 2020 21:02:04 -0700 Subject: [PATCH 086/362] A little more cleanup --- controller/PostgreSQL.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 31573705d..2a512bbc2 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1483,8 +1483,10 @@ void PostgreSQL::commitThread() std::string key = "networks:{" + controllerId + "}"; if (_rc->clusterMode) { _cluster->srem(key, id); + _cluster->del("network-nodes-online:{"+controllerId+"}:"+id); } else { _redis->srem(key, id); + _redis->del("network-nodes-online:{"+controllerId+"}:"+id); } } catch (sw::redis::Error &e) { fprintf(stderr, "ERROR: Error adding network to Redis: %s\n", e.what()); @@ -1525,8 +1527,10 @@ void PostgreSQL::commitThread() std::string key = "network-nodes-all:{" + controllerId + "}:" + networkId; if (_rc->clusterMode) { _cluster->srem(key, memberId); + _cluster->del("member:{"+controllerId+"}:"+networkId+":"+memberId); } else { _redis->srem(key, memberId); + _redis->del("member:{"+controllerId+"}:"+networkId+":"+memberId); } } catch (sw::redis::Error &e) { fprintf(stderr, "ERROR: Error deleting member from Redis: %s\n", e.what()); From 06de25a6802aa88b3576a5e3d917cf41cd5f50e8 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 27 May 2020 22:25:07 -0700 Subject: [PATCH 087/362] bump online notification pause to 100ms also put all online notification redis commands into a single tx --- controller/PostgreSQL.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 2a512bbc2..22049389a 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1590,7 +1590,7 @@ void PostgreSQL::onlineNotificationThread() fprintf(stderr, "Error in online notification thread (redis): %s\n", e.what()); #endif } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } fprintf(stderr, "%s: Fell out of run loop in onlineNotificationThread\n", _myAddressStr.c_str()); @@ -1705,7 +1705,7 @@ void PostgreSQL::onlineNotification_Postgres(PGconn *conn, std::unordered_map< s PQclear(res); } - // std::this_thread::sleep_for(std::chrono::milliseconds(10)); + // std::this_thread::sleep_for(std::chrono::milliseconds(100)); // } // fprintf(stderr, "%s: Fell out of run loop in onlineNotificationThread\n", _myAddressStr.c_str()); // PQfinish(conn); @@ -1743,7 +1743,7 @@ void PostgreSQL::onlineNotification_Redis() fprintf(stderr, "Error in online notification thread (redis): %s\n", e.what()); #endif } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } @@ -1785,8 +1785,6 @@ void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &control .hmset("member:{"+controllerId+"}:"+networkId+":"+memberId, record.begin(), record.end()); } - tx.exec(); - // expire records from all-nodes and network-nodes member list uint64_t expireOld = OSUtils::now() - 300000; From 135a547889122472bb0004c4f0e6d81d435b9524 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 28 May 2020 19:22:07 -0700 Subject: [PATCH 088/362] No longer update both pgsql & redis --- controller/PostgreSQL.cpp | 82 +++++++++++---------------------------- controller/PostgreSQL.hpp | 2 +- 2 files changed, 24 insertions(+), 60 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 22049389a..a9f9500bb 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1558,6 +1558,15 @@ void PostgreSQL::onlineNotificationThread() { waitForReady(); + if (_rc != NULL) { + onlineNotification_Redis(); + } else { + onlineNotification_Postgres(); + } +} + +void PostgreSQL::onlineNotification_Postgres() +{ PGconn *conn = getPgConn(); if (PQstatus(conn) == CONNECTION_BAD) { fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn)); @@ -1566,64 +1575,19 @@ void PostgreSQL::onlineNotificationThread() } _connected = 1; - while (_run == 1) { - std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; - { - std::lock_guard l(_lastOnline_l); - lastOnline.swap(_lastOnline); - } - - onlineNotification_Postgres(conn, lastOnline); - - try { - if (!lastOnline.empty()) { - if (_rc->clusterMode) { - auto tx = _cluster->transaction(_myAddressStr, true); - _doRedisUpdate(tx, _myAddressStr, lastOnline); - } else { - auto tx = _redis->transaction(true); - _doRedisUpdate(tx, _myAddressStr, lastOnline); - } - } - } catch (sw::redis::Error &e) { -#ifdef ZT_TRACE - fprintf(stderr, "Error in online notification thread (redis): %s\n", e.what()); -#endif - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - fprintf(stderr, "%s: Fell out of run loop in onlineNotificationThread\n", _myAddressStr.c_str()); - PQfinish(conn); - if (_run == 1) { - fprintf(stderr, "ERROR: %s onlineNotificationThread should still be running! Exiting Controller.\n", _myAddressStr.c_str()); - exit(6); - } - - // if (_rc != NULL) { - // onlineNotification_Redis(); - // } else { - // onlineNotification_Postgres(); - // } -} - -void PostgreSQL::onlineNotification_Postgres(PGconn *conn, std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline) -{ - - nlohmann::json jtmp1, jtmp2; - // while (_run == 1) { + while (_run == 1) { if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "ERROR: Online Notification thread lost connection to Postgres."); PQfinish(conn); exit(5); } - // std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; - // { - // std::lock_guard l(_lastOnline_l); - // lastOnline.swap(_lastOnline); - // } + std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; + { + std::lock_guard l(_lastOnline_l); + lastOnline.swap(_lastOnline); + } PGresult *res = NULL; @@ -1705,14 +1669,14 @@ void PostgreSQL::onlineNotification_Postgres(PGconn *conn, std::unordered_map< s PQclear(res); } - // std::this_thread::sleep_for(std::chrono::milliseconds(100)); - // } - // fprintf(stderr, "%s: Fell out of run loop in onlineNotificationThread\n", _myAddressStr.c_str()); - // PQfinish(conn); - // if (_run == 1) { - // fprintf(stderr, "ERROR: %s onlineNotificationThread should still be running! Exiting Controller.\n", _myAddressStr.c_str()); - // exit(6); - // } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + fprintf(stderr, "%s: Fell out of run loop in onlineNotificationThread\n", _myAddressStr.c_str()); + PQfinish(conn); + if (_run == 1) { + fprintf(stderr, "ERROR: %s onlineNotificationThread should still be running! Exiting Controller.\n", _myAddressStr.c_str()); + exit(6); + } } void PostgreSQL::onlineNotification_Redis() diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index 061349e1e..f61670132 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -70,7 +70,7 @@ private: void commitThread(); void onlineNotificationThread(); - void onlineNotification_Postgres(PGconn *conn, std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline); + void onlineNotification_Postgres(); void onlineNotification_Redis(); void _doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId, std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline); From 5e122b95e7cb594464ee2090ad0ab1a403f09367 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Sat, 30 May 2020 21:21:22 -0700 Subject: [PATCH 089/362] Fix segfault during balance-rr when link is brought down --- node/Bond.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/node/Bond.cpp b/node/Bond.cpp index 9aef8f815..28b998049 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -135,13 +135,14 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) int _tempIdx = _rrIdx; for (int searchCount = 0; searchCount < (_numBondedPaths-1); searchCount++) { _tempIdx = (_tempIdx == (_numBondedPaths-1)) ? 0 : _tempIdx+1; - if (_paths[_bondedIdx[_tempIdx]] && _paths[_bondedIdx[_tempIdx]]->eligible(now,_ackSendInterval)) { - _rrIdx = _tempIdx; - break; + if (_bondedIdx[_tempIdx] != ZT_MAX_PEER_NETWORK_PATHS) { + if (_paths[_bondedIdx[_tempIdx]] && _paths[_bondedIdx[_tempIdx]]->eligible(now,_ackSendInterval)) { + _rrIdx = _tempIdx; + break; + } } } } - fprintf(stderr, "_rrIdx=%d\n", _rrIdx); if (_paths[_bondedIdx[_rrIdx]]) { return _paths[_bondedIdx[_rrIdx]]; } From 1dca7b92cf2b04a4a592eaa58711c461aa22bd20 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Sun, 31 May 2020 17:30:41 -0700 Subject: [PATCH 090/362] Remove exit condition for bond creation during re-learning of previously-known paths --- node/BondController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/BondController.cpp b/node/BondController.cpp index 4bc8d2261..cb4414f9f 100644 --- a/node/BondController.cpp +++ b/node/BondController.cpp @@ -107,7 +107,7 @@ SharedPtr BondController::createTransportTriggeredBond(const RuntimeEnviro } } else { - fprintf(stderr, "bond already exists for %llx, cannot re-register. exiting\n", identity); exit(0); // TODO: Remove + fprintf(stderr, "bond already exists for %llx.\n", identity); } if (bond) { _bonds[identity] = bond; From fa5c8ef434a31e91f02861bb169f22a9aae7b1f4 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 1 Jun 2020 22:58:58 -0700 Subject: [PATCH 091/362] Fix timers, fix flow count discrepancy after flow removal, fix balance-aware flow re-assignment when one or more links go down --- node/Bond.cpp | 217 +++++++++++++++++++++------------------- node/Bond.hpp | 5 +- node/BondController.cpp | 4 +- 3 files changed, 117 insertions(+), 109 deletions(-) diff --git a/node/Bond.cpp b/node/Bond.cpp index 28b998049..9a2f5c267 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -25,7 +25,7 @@ Bond::Bond(const RuntimeEnvironment *renv, int policy, const SharedPtr& pe RR(renv), _peer(peer) { - setReasonableDefaults(policy); + setReasonableDefaults(policy, SharedPtr(), false); _policyAlias = BondController::getPolicyStrByCode(policy); } @@ -34,31 +34,14 @@ Bond::Bond(const RuntimeEnvironment *renv, std::string& basePolicy, std::string& _policyAlias(policyAlias), _peer(peer) { - setReasonableDefaults(BondController::getPolicyCodeByStr(basePolicy)); + setReasonableDefaults(BondController::getPolicyCodeByStr(basePolicy), SharedPtr(), false); } -Bond::Bond(const RuntimeEnvironment *renv, const Bond &originalBond, const SharedPtr& peer) : +Bond::Bond(const RuntimeEnvironment *renv, SharedPtr originalBond, const SharedPtr& peer) : RR(renv), _peer(peer) { - // First, set everything to sane defaults - setReasonableDefaults(originalBond._bondingPolicy); - _policyAlias = originalBond._policyAlias; - // Second, apply user specified values (only if they make sense) - _downDelay = originalBond._downDelay; - _upDelay = originalBond._upDelay; - if (originalBond._bondMonitorInterval > 0 && originalBond._bondMonitorInterval < 65535) { - _bondMonitorInterval = originalBond._bondMonitorInterval; - } - else { - fprintf(stderr, "warning: bondMonitorInterval (%d) is out of range, using default (%d)\n", originalBond._bondMonitorInterval, _bondMonitorInterval); - } - if (originalBond._slaveMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_PASSIVE - && originalBond._failoverInterval != 0) { - fprintf(stderr, "warning: passive path monitoring was specified, this will prevent failovers from happening in a timely manner.\n"); - } - _abSlaveSelectMethod = originalBond._abSlaveSelectMethod; - memcpy(_qualityWeights, originalBond._qualityWeights, ZT_QOS_WEIGHT_SIZE * sizeof(float)); + setReasonableDefaults(originalBond->_bondingPolicy, originalBond, true); } void Bond::nominatePath(const SharedPtr& path, int64_t now) @@ -97,7 +80,7 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) /** * active-backup */ - if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP) { + if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { if (_abPath) { return _abPath; } @@ -105,7 +88,7 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) /** * broadcast */ - if (_bondingPolicy== ZT_BONDING_POLICY_BROADCAST) { + if (_bondingPolicy == ZT_BONDING_POLICY_BROADCAST) { return SharedPtr(); // Handled in Switch::_trySend() } if (!_numBondedPaths) { @@ -114,7 +97,7 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) /** * balance-rr */ - if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_RR) { + if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR) { if (!_allowFlowHashing) { //fprintf(stderr, "_rrPacketsSentOnCurrSlave=%d, _numBondedPaths=%d, _rrIdx=%d\n", _rrPacketsSentOnCurrSlave, _numBondedPaths, _rrIdx); if (_packetsPerSlave == 0) { @@ -151,7 +134,7 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) /** * balance-xor */ - if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_XOR || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE) { + if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { if (!_allowFlowHashing || flowId == -1) { // No specific path required for unclassified traffic, send on anything return _paths[_bondedIdx[_freeRandomByte % _numBondedPaths]]; // TODO: Optimize @@ -252,9 +235,9 @@ void Bond::recordIncomingPacket(const SharedPtr& path, uint64_t packetId, * which path to use. */ if ((flowId != ZT_QOS_NO_FLOW) - && (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_RR - || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_XOR - || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE)) { + && (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR + || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR + || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE)) { Mutex::Lock _l(_flows_m); SharedPtr flow; if (!_flows.count(flowId)) { @@ -335,6 +318,7 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) unsigned int idx = ZT_MAX_PEER_NETWORK_PATHS; if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR) { idx = abs((int)(flow->id() % (_numBondedPaths))); + //fprintf(stderr, "flow->id()=%d, %x, _numBondedPaths=%d, idx=%d\n", flow->id(), flow->id(), _numBondedPaths, idx); flow->assignPath(_paths[_bondedIdx[idx]],now); } if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { @@ -347,15 +331,28 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) fprintf(stderr, "no bonded paths for flow assignment\n"); return false; } + /* Since there may be scenarios where a path is removed before we can re-estimate + relative qualities (and thus allocations) we need to down-modulate the entropy + value that we use to randomly assign among the surviving paths, otherwise we risk + not being able to find a path to assign this flow to. */ + int totalIncompleteAllocation = 0; + for(unsigned int i=0;ibonded()) { + totalIncompleteAllocation += _paths[i]->_allocation; + } + } + fprintf(stderr, "entropy = %d, totalIncompleteAllocation=%d\n", entropy, totalIncompleteAllocation); + entropy %= totalIncompleteAllocation; + fprintf(stderr, "new entropy = %d\n", entropy); for(unsigned int i=0;ibonded()) { SharedPtr slave = RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); _paths[i]->address().toString(curPathStr); uint8_t probabilitySegment = (_totalBondUnderload > 0) ? _paths[i]->_affinity : _paths[i]->_allocation; - //fprintf(stderr, "i=%2d, entropy=%3d, alloc=%3d, byteload=%4d, segment=%3d, _totalBondUnderload=%3d, ifname=%s, path=%20s\n", i, entropy, _paths[i]->allocation, _paths[i]->relativeByteLoad, probabilitySegment, _totalBondUnderload, slave->ifname().c_str(), curPathStr); + fprintf(stderr, "i=%2d, entropy=%3d, alloc=%3d, byteload=%4d, segment=%3d, _totalBondUnderload=%3d, ifname=%s, path=%20s\n", i, entropy, _paths[i]->_allocation, _paths[i]->_relativeByteLoad, probabilitySegment, _totalBondUnderload, slave->ifname().c_str(), curPathStr); if (entropy <= probabilitySegment) { idx = i; - //fprintf(stderr, "\t is best path\n"); + fprintf(stderr, "\t is best path\n"); break; } entropy -= probabilitySegment; @@ -423,6 +420,7 @@ void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) while (it != _flows.end()) { if (it->second->age(now) > age) { fprintf(stderr, "forgetting flow %x between this node and %llx, %lu active flow(s)\n", it->first, _peer->_id.address().toInt(), (_flows.size()-1)); + it->second->assignedPath()->_assignedFlowCount--; it = _flows.erase(it); } else { ++it; @@ -440,10 +438,10 @@ void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) } if (oldestFlow != _flows.end()) { fprintf(stderr, "forgetting oldest flow %x (of age %llu) between this node and %llx, %lu active flow(s)\n", oldestFlow->first, oldestFlow->second->age(now), _peer->_id.address().toInt(), (_flows.size()-1)); + oldestFlow->second->assignedPath()->_assignedFlowCount--; _flows.erase(oldestFlow); } } - fprintf(stderr, "000\n"); } void Bond::processIncomingPathNegotiationRequest(uint64_t now, SharedPtr &path, int16_t remoteUtility) @@ -610,17 +608,17 @@ void Bond::processBackgroundTasks(void *tPtr, const int64_t now) //fprintf(stderr, "_lastFrame=%llu, suggestedMonitorInterval=%d, _dynamicPathMonitorInterval=%d\n", // (now-_lastFrame), suggestedMonitorInterval, _dynamicPathMonitorInterval); } - + // TODO: Clarify and generalize this logic if (_slaveMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC) { _shouldCollectPathStatistics = true; } // Memoize oft-used properties in the packet ingress/egress logic path - if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE) { + if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { // Required for real-time balancing _shouldCollectPathStatistics = true; } - if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP) { + if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { if (_abSlaveSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_BETTER) { // Required for judging suitability of primary slave after recovery _shouldCollectPathStatistics = true; @@ -680,7 +678,7 @@ void Bond::processBackgroundTasks(void *tPtr, const int64_t now) void Bond::applyUserPrefs() { - fprintf(stderr, "applyUserPrefs, _minReqPathMonitorInterval=%d\n", RR->bc->minReqPathMonitorInterval()); + //fprintf(stderr, "applyUserPrefs, _minReqPathMonitorInterval=%d\n", RR->bc->minReqPathMonitorInterval()); for(unsigned int i=0;ibc->getBondStartTime())), rebuildBond); + //fprintf(stderr, "%lu curateBond (rebuildBond=%d), _numBondedPaths=%d\n", ((now - RR->bc->getBondStartTime())), rebuildBond, _numBondedPaths); char pathStr[128]; /** * Update path states @@ -727,6 +725,9 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) continue; } bool currEligibility = _paths[i]->eligible(now,_ackSendInterval); + //_paths[i]->address().toString(pathStr); + //fprintf(stderr, "\n\n%ld path eligibility (for %s, %s):\n", (RR->node->now() - RR->bc->getBondStartTime()), getSlave(_paths[i])->ifname().c_str(), pathStr); + //_paths[i]->printEligible(now,_ackSendInterval); if (currEligibility != _paths[i]->_lastEligibilityState) { _paths[i]->address().toString(pathStr); //fprintf(stderr, "\n\n%ld path eligibility (for %s, %s) has changed (from %d to %d)\n", (RR->node->now() - RR->bc->getBondStartTime()), getSlave(_paths[i])->ifname().c_str(), pathStr, _paths[i]->lastCheckedEligibility, _paths[i]->eligible(now,_ackSendInterval)); @@ -754,9 +755,9 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) * Curate the set of paths that are part of the bond proper. Selects a single path * per logical slave according to eligibility and user-specified constraints. */ - if ((_bondingPolicy== ZT_BONDING_POLICY_BALANCE_RR) - || (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_XOR) - || (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE)) { + if ((_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR) + || (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR) + || (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE)) { if (!_numBondedPaths) { rebuildBond = true; } @@ -822,7 +823,7 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) } _numBondedPaths = updatedBondedPathCount; - if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_RR) { + if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR) { // Cause a RR reset since the currently used index might no longer be valid _rrPacketsSentOnCurrSlave = _packetsPerSlave; } @@ -975,11 +976,9 @@ void Bond::estimatePathQuality(const int64_t now) _paths[i]->_allocation = alloc[i]; } } - /* if ((now - _lastLogTS) > 500) { if (!relevant()) {return;} //fprintf(stderr, "\n"); - _lastPrintTS = now; _lastLogTS = now; int numPlottablePaths=0; for(unsigned int i=0;iaddress().toString(pathStr); fprintf(stdout, "%s, %s, %8.3f, %8.3f, %8.3f, %5.3f, %5.3f, %5.3f, %8f, %5.3f, %5.3f, %d, %5.3f, %d, %d, %d, %d, %d, %d, ", - getSlave(_paths[i])->ifname().c_str(), pathStr, _paths[i]->latencyMean, lat[i],pdv[i], _paths[i]->packetLossRatio, plr[i],per[i],thr[i],thm[i],thv[i],(now - _paths[i]->lastIn()),quality[i],alloc[i], - _paths[i]->relativeByteLoad, _paths[i]->assignedFlowCount, _paths[i]->alive(now, true), _paths[i]->eligible(now,_ackSendInterval), _paths[i]->qosStatsOut.size()); + getSlave(_paths[i])->ifname().c_str(), pathStr, _paths[i]->_latencyMean, lat[i],pdv[i], _paths[i]->_packetLossRatio, plr[i],per[i],thr[i],thm[i],thv[i],(now - _paths[i]->lastIn()),quality[i],alloc[i], + _paths[i]->_relativeByteLoad, _paths[i]->_assignedFlowCount, _paths[i]->alive(now, true), _paths[i]->eligible(now,_ackSendInterval), _paths[i]->qosStatsOut.size()); } } fprintf(stdout, "\n"); } - */ } void Bond::processBalanceTasks(const int64_t now) @@ -1047,7 +1045,7 @@ void Bond::processBalanceTasks(const int64_t now) /** * Re-allocate flows from dead paths */ - if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_XOR || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE) { + if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { Mutex::Lock _l(_flows_m); for (int i=0;ibonded()) { if (totalBytes) { @@ -1139,7 +1138,7 @@ void Bond::processBalanceTasks(const int64_t now) } } } - +*/ //fprintf(stderr, "_totalBondUnderload=%d (end)\n\n", _totalBondUnderload); /** @@ -1502,7 +1501,7 @@ void Bond::processActiveBackupTasks(const int64_t now) } } -void Bond::setReasonableDefaults(int policy) +void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool useTemplate) { // If invalid bonding policy, try default int _defaultBondingPolicy = BondController::defaultBondingPolicy(); @@ -1548,7 +1547,10 @@ void Bond::setReasonableDefaults(int policy) _lastFrame=0; - + // TODO: Remove + _header=false; + _lastLogTS = RR->node->now(); + _lastPrintTS = RR->node->now(); /** * Paths are actively monitored to provide a real-time quality/preference-ordered rapid failover queue. @@ -1635,18 +1637,53 @@ void Bond::setReasonableDefaults(int policy) break; } + if (useTemplate) { + _policyAlias = templateBond->_policyAlias; + _failoverInterval = templateBond->_failoverInterval; + _downDelay = templateBond->_downDelay; + _upDelay = templateBond->_upDelay; + + fprintf(stderr, "TIMERS: strat=%d, fi= %d, bmi= %d, qos= %d, ack= %d, estimateInt= %d, refractory= %d, ud= %d, dd= %d\n", + _slaveMonitorStrategy, + _failoverInterval, + _bondMonitorInterval, + _qosSendInterval, + _ackSendInterval, + _qualityEstimationInterval, + _defaultPathRefractoryPeriod, + _upDelay, + _downDelay); + + if (templateBond->_slaveMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_PASSIVE + && templateBond->_failoverInterval != 0) { + fprintf(stderr, "warning: passive path monitoring was specified, this will prevent failovers from happening in a timely manner.\n"); + } + _abSlaveSelectMethod = templateBond->_abSlaveSelectMethod; + memcpy(_qualityWeights, templateBond->_qualityWeights, ZT_QOS_WEIGHT_SIZE * sizeof(float)); + } + + + // + // Second, apply user specified values (only if they make sense) + /** * Timer geometries and counters */ + // TODO: Think more about the maximum + /* + if (originalBond._failoverInterval > 250 && originalBond._failoverInterval < 65535) { + _failoverInterval = originalBond._failoverInterval; + } + else { + fprintf(stderr, "warning: _failoverInterval (%d) is out of range, using default (%d)\n", originalBond._failoverInterval, _failoverInterval); + } + */ + _bondMonitorInterval = _failoverInterval / 3; + BondController::setMinReqPathMonitorInterval(_bondMonitorInterval); _ackSendInterval = _failoverInterval; _qualityEstimationInterval = _failoverInterval * 2; - _dynamicPathMonitorInterval = 0; - - _downDelay=0; - _upDelay=0; - _ackCutoffCount = 0; _lastAckRateCheck = 0; _qosSendInterval = _bondMonitorInterval * 4; @@ -1654,33 +1691,7 @@ void Bond::setReasonableDefaults(int policy) _lastQoSRateCheck = 0; _lastQualityEstimation=0; throughputMeasurementInterval = _ackSendInterval * 2; - BondController::setMinReqPathMonitorInterval(_bondMonitorInterval); - _defaultPathRefractoryPeriod = 8000; - - - - - - // TODO: Remove - _header=false; - _lastLogTS = 0; - _lastPrintTS = 0; - - - - fprintf(stderr, "TIMERS: strat=%d, fi= %d, bmi= %d, qos= %d, ack= %d, estimateInt= %d, refractory= %d, ud= %d, dd= %d\n", - _slaveMonitorStrategy, - _failoverInterval, - _bondMonitorInterval, - _qosSendInterval, - _ackSendInterval, - _qualityEstimationInterval, - _defaultPathRefractoryPeriod, - _upDelay, - _downDelay); - - } void Bond::setUserQualityWeights(float weights[], int len) @@ -1721,22 +1732,20 @@ void Bond::dumpInfo(const int64_t now) fprintf(stderr, "---[ bp=%d, id=%llx, dd=%d, up=%d, pmi=%d, specifiedSlaves=%d, _specifiedPrimarySlave=%d, _specifiedFailInst=%d ]\n", _policy, _peer->identity().address().toInt(), _downDelay, _upDelay, _monitorInterval, _userHasSpecifiedSlaves, _userHasSpecifiedPrimarySlave, _userHasSpecifiedFailoverInstructions); - if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP) { + if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { fprintf(stderr, "Paths (bp=%d, stats=%d, primaryReselect=%d) :\n", _policy, _shouldCollectPathStatistics, _abSlaveSelectMethod); } - if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_RR - || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_XOR - || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE) { + if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR + || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR + || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { fprintf(stderr, "Paths (bp=%d, stats=%d, fh=%d) :\n", _policy, _shouldCollectPathStatistics, _allowFlowHashing); }*/ - - if ((now - _lastLogTS) < 1000) { + if ((now - _lastPrintTS) < 1000) { return; } _lastPrintTS = now; - _lastLogTS = now; fprintf(stderr, "\n\n"); @@ -1792,21 +1801,21 @@ void Bond::dumpInfo(const int64_t now) } else { fprintf(stderr, " "); } - if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP && _abPath && (_abPath == _paths[i].ptr())) { + if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP && _abPath && (_abPath == _paths[i].ptr())) { fprintf(stderr, " ACTIVE "); - } else if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP) { + } else if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { fprintf(stderr, " "); } - if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP && _abFailoverQueue.size() && (_abFailoverQueue.front().ptr() == _paths[i].ptr())) { + if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP && _abFailoverQueue.size() && (_abFailoverQueue.front().ptr() == _paths[i].ptr())) { fprintf(stderr, " NEXT "); - } else if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP) { + } else if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { fprintf(stderr, " "); } fprintf(stderr, "%5s %s\n", slave->ifname().c_str(), pathStr); } } - if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP) { + if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { if (!_abFailoverQueue.empty()) { fprintf(stderr, "\nFailover Queue:\n"); for (std::list >::iterator it(_abFailoverQueue.begin()); it!=_abFailoverQueue.end();++it) { @@ -1827,28 +1836,26 @@ void Bond::dumpInfo(const int64_t now) } } - if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_RR - || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_XOR - || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE) { - /* + if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR + || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR + || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { if (_numBondedPaths) { fprintf(stderr, "\nBonded Paths:\n"); for (int i=0; i<_numBondedPaths; ++i) { - _paths[_bondedIdx[i]].p->address().toString(currPathStr); - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[_bondedIdx[i]].p->localSocket()); + _paths[_bondedIdx[i]]->address().toString(currPathStr); + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[_bondedIdx[i]]->localSocket()); fprintf(stderr, " [%d]\t%8s\tflows=%3d\tspeed=%7d\trelSpeed=%3d\tipvPref=%3d\tfscore=%9d\t\t%s\n", i, //fprintf(stderr, " [%d]\t%8s\tspeed=%7d\trelSpeed=%3d\tflowCount=%2d\tipvPref=%3d\tfscore=%9d\t\t%s\n", i, slave->ifname().c_str(), - numberOfAssignedFlows(_paths[_bondedIdx[i]].p), + _paths[_bondedIdx[i]]->_assignedFlowCount, slave->speed(), slave->relativeSpeed(), //_paths[_bondedIdx[i]].p->assignedFlows.size(), slave->ipvPref(), - _paths[_bondedIdx[i]].p->failoverScore(), + _paths[_bondedIdx[i]]->_failoverScore, currPathStr); } } - */ /* if (_allowFlowHashing) { //Mutex::Lock _l(_flows_m); diff --git a/node/Bond.hpp b/node/Bond.hpp index 62195b18e..e60e27a19 100644 --- a/node/Bond.hpp +++ b/node/Bond.hpp @@ -79,7 +79,7 @@ public: * @param original * @param peer */ - Bond(const RuntimeEnvironment *renv, const Bond &original, const SharedPtr& peer); + Bond(const RuntimeEnvironment *renv, SharedPtr originalBond, const SharedPtr& peer); /** * @return The human-readable name of the bonding policy @@ -293,8 +293,9 @@ public: * user-specified parameters. * * @param policy Bonding policy + * @param templateBond */ - void setReasonableDefaults(int policy); + void setReasonableDefaults(int policy, SharedPtr templateBond, bool useTemplate); /** * Check and assign user-specified quality weights to this bond. diff --git a/node/BondController.cpp b/node/BondController.cpp index cb4414f9f..06da41759 100644 --- a/node/BondController.cpp +++ b/node/BondController.cpp @@ -92,7 +92,7 @@ SharedPtr BondController::createTransportTriggeredBond(const RuntimeEnviro } if (!_defaultBondingPolicy && _defaultBondingPolicyStr.length()) { fprintf(stderr, " no assignment, using default custom (%s)\n", _defaultBondingPolicyStr.c_str()); - bond = new Bond(renv, *(_bondPolicyTemplates[_defaultBondingPolicyStr].ptr()), peer); + bond = new Bond(renv, _bondPolicyTemplates[_defaultBondingPolicyStr].ptr(), peer); } } else { @@ -102,7 +102,7 @@ SharedPtr BondController::createTransportTriggeredBond(const RuntimeEnviro bond = new Bond(renv, _defaultBondingPolicy, peer); } else { - bond = new Bond(renv, *(_bondPolicyTemplates[_policyTemplateAssignments[identity]].ptr()), peer); + bond = new Bond(renv, _bondPolicyTemplates[_policyTemplateAssignments[identity]].ptr(), peer); } } } From 17c7ae20cc334adefbec5e32cfc7278b2ea7c7f2 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 4 Jun 2020 13:46:16 -0700 Subject: [PATCH 092/362] force add libredis++.a for mac --- .../install/macos/lib/libredis++.a | Bin 0 -> 706784 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/lib/libredis++.a diff --git a/ext/redis-plus-plus-1.1.1/install/macos/lib/libredis++.a b/ext/redis-plus-plus-1.1.1/install/macos/lib/libredis++.a new file mode 100644 index 0000000000000000000000000000000000000000..af027a63e481434ffbb226e94795a5793edee052 GIT binary patch literal 706784 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 Date: Thu, 4 Jun 2020 14:06:30 -0700 Subject: [PATCH 093/362] handle case of no networks and/or no members --- controller/PostgreSQL.cpp | 44 +++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index a9f9500bb..87d3db7e4 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -437,14 +437,16 @@ void PostgreSQL::initializeNetworks(PGconn *conn) PQclear(res); - if (_rc && _rc->clusterMode) { - auto tx = _cluster->transaction(_myAddressStr, true); - tx.sadd(setKey, networkSet.begin(), networkSet.end()); - tx.exec(); - } else if (_rc && !_rc->clusterMode) { - auto tx = _redis->transaction(true); - tx.sadd(setKey, networkSet.begin(), networkSet.end()); - tx.exec(); + if(!networkSet.empty()) { + if (_rc && _rc->clusterMode) { + auto tx = _cluster->transaction(_myAddressStr, true); + tx.sadd(setKey, networkSet.begin(), networkSet.end()); + tx.exec(); + } else if (_rc && !_rc->clusterMode) { + auto tx = _redis->transaction(true); + tx.sadd(setKey, networkSet.begin(), networkSet.end()); + tx.exec(); + } } if (++this->_ready == 2) { @@ -643,19 +645,21 @@ void PostgreSQL::initializeMembers(PGconn *conn) PQclear(res); - if (_rc != NULL) { - if (_rc->clusterMode) { - auto tx = _cluster->transaction(_myAddressStr, true); - for (auto it : networkMembers) { - tx.sadd(it.first, it.second); + if (!networkMembers.empty()) { + if (_rc != NULL) { + if (_rc->clusterMode) { + auto tx = _cluster->transaction(_myAddressStr, true); + for (auto it : networkMembers) { + tx.sadd(it.first, it.second); + } + tx.exec(); + } else { + auto tx = _redis->transaction(true); + for (auto it : networkMembers) { + tx.sadd(it.first, it.second); + } + tx.exec(); } - tx.exec(); - } else { - auto tx = _redis->transaction(true); - for (auto it : networkMembers) { - tx.sadd(it.first, it.second); - } - tx.exec(); } } if (++this->_ready == 2) { From d0aacfddb796c00f5a49555c8aa34c7866fc3a6a Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 4 Jun 2020 14:12:20 -0700 Subject: [PATCH 094/362] one more spot --- controller/PostgreSQL.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 87d3db7e4..0f5fde77b 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -485,18 +485,20 @@ void PostgreSQL::initializeMembers(PGconn *conn) deletes.insert(key); } - if (_rc->clusterMode) { - auto tx = _cluster->transaction(_myAddressStr, true); - for (std::string k : deletes) { - tx.del(k); + if (!deletes.empty()) { + if (_rc->clusterMode) { + auto tx = _cluster->transaction(_myAddressStr, true); + for (std::string k : deletes) { + tx.del(k); + } + tx.exec(); + } else { + auto tx = _redis->transaction(true); + for (std::string k : deletes) { + tx.del(k); + } + tx.exec(); } - tx.exec(); - } else { - auto tx = _redis->transaction(true); - for (std::string k : deletes) { - tx.del(k); - } - tx.exec(); } } From 5f0ee4fc78f438d00f382b4e29ebdde621e7e160 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 16 Jun 2020 12:30:21 -0700 Subject: [PATCH 095/362] Fix invalid defaultBondingPolicy conditions, Add ZT_MultipathFlowRebalanceStrategy, Add basic hysteresis mechanism to flow re-assignment --- include/ZeroTierOne.h | 27 ++++ node/Bond.cpp | 270 ++++++++++++++++++---------------------- node/Bond.hpp | 10 +- node/BondController.cpp | 5 +- node/Constants.hpp | 5 - node/Flow.hpp | 1 + node/IncomingPacket.cpp | 2 + node/Peer.cpp | 3 +- service/OneService.cpp | 1 + 9 files changed, 166 insertions(+), 158 deletions(-) diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index 890e56048..dfb520469 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -521,6 +521,33 @@ enum ZT_MultipathMonitorStrategy ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC = 3 }; +/** + * Strategy for re-balancing protocol flows + */ +enum ZT_MultipathFlowRebalanceStrategy +{ + /** + * Flows will only be re-balanced among slaves during + * assignment or failover. This minimizes the possibility + * of sequence reordering and is thus the default setting. + */ + ZT_MULTIPATH_FLOW_REBALANCE_STRATEGY_PASSIVE = 0, + + /** + * Flows that are active may be re-assigned to a new more + * suitable slave if it can be done without disrupting the flow. + * This setting can sometimes cause sequence re-ordering. + */ + ZT_MULTIPATH_FLOW_REBALANCE_STRATEGY_OPPORTUNISTIC = 0, + + /** + * Flows will be continuously re-assigned the most suitable slave + * in order to maximize "balance". This can often cause sequence + * reordering and is thus only reccomended for protocols like UDP. + */ + ZT_MULTIPATH_FLOW_REBALANCE_STRATEGY_AGGRESSIVE = 2 +}; + /** * Indices for the path quality weight vector */ diff --git a/node/Bond.cpp b/node/Bond.cpp index 9a2f5c267..656285925 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -25,6 +25,10 @@ Bond::Bond(const RuntimeEnvironment *renv, int policy, const SharedPtr& pe RR(renv), _peer(peer) { + // TODO: Remove for production + _header=false; + _lastLogTS = RR->node->now(); + _lastPrintTS = RR->node->now(); setReasonableDefaults(policy, SharedPtr(), false); _policyAlias = BondController::getPolicyStrByCode(policy); } @@ -41,6 +45,10 @@ Bond::Bond(const RuntimeEnvironment *renv, SharedPtr originalBond, const S RR(renv), _peer(peer) { + // TODO: Remove for production + _header=false; + _lastLogTS = RR->node->now(); + _lastPrintTS = RR->node->now(); setReasonableDefaults(originalBond->_bondingPolicy, originalBond, true); } @@ -162,7 +170,7 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) void Bond::recordIncomingInvalidPacket(const SharedPtr& path) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordIncomingInvalidPacket() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + // char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordIncomingInvalidPacket() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); Mutex::Lock _l(_paths_m); for (int i=0; i& path) void Bond::recordOutgoingPacket(const SharedPtr &path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordOutgoingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getSlave(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); + // char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordOutgoingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getSlave(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); _freeRandomByte += (unsigned char)(packetId >> 8); // Grab entropy to use in path selection logic if (!_shouldCollectPathStatistics) { return; @@ -320,6 +328,7 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) idx = abs((int)(flow->id() % (_numBondedPaths))); //fprintf(stderr, "flow->id()=%d, %x, _numBondedPaths=%d, idx=%d\n", flow->id(), flow->id(), _numBondedPaths, idx); flow->assignPath(_paths[_bondedIdx[idx]],now); + ++(_paths[_bondedIdx[idx]]->_assignedFlowCount); } if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { unsigned char entropy; @@ -341,29 +350,32 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) totalIncompleteAllocation += _paths[i]->_allocation; } } - fprintf(stderr, "entropy = %d, totalIncompleteAllocation=%d\n", entropy, totalIncompleteAllocation); + //fprintf(stderr, "entropy = %d, totalIncompleteAllocation=%d\n", entropy, totalIncompleteAllocation); entropy %= totalIncompleteAllocation; - fprintf(stderr, "new entropy = %d\n", entropy); + //fprintf(stderr, "new entropy = %d\n", entropy); for(unsigned int i=0;ibonded()) { SharedPtr slave = RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); _paths[i]->address().toString(curPathStr); uint8_t probabilitySegment = (_totalBondUnderload > 0) ? _paths[i]->_affinity : _paths[i]->_allocation; - fprintf(stderr, "i=%2d, entropy=%3d, alloc=%3d, byteload=%4d, segment=%3d, _totalBondUnderload=%3d, ifname=%s, path=%20s\n", i, entropy, _paths[i]->_allocation, _paths[i]->_relativeByteLoad, probabilitySegment, _totalBondUnderload, slave->ifname().c_str(), curPathStr); + //fprintf(stderr, "i=%2d, entropy=%3d, alloc=%3d, byteload=%4d, segment=%3d, _totalBondUnderload=%3d, ifname=%s, path=%20s\n", i, entropy, _paths[i]->_allocation, _paths[i]->_relativeByteLoad, probabilitySegment, _totalBondUnderload, slave->ifname().c_str(), curPathStr); if (entropy <= probabilitySegment) { idx = i; - fprintf(stderr, "\t is best path\n"); + //fprintf(stderr, "\t is best path\n"); break; } entropy -= probabilitySegment; } } if (idx < ZT_MAX_PEER_NETWORK_PATHS) { + if (flow->_assignedPath) { + flow->_previouslyAssignedPath = flow->_assignedPath; + } flow->assignPath(_paths[idx],now); ++(_paths[idx]->_assignedFlowCount); } else { - fprintf(stderr, "could not assign flow?\n"); exit(0); // TODO: Remove + fprintf(stderr, "could not assign flow?\n"); exit(0); // TODO: Remove for production return false; } } @@ -397,6 +409,7 @@ SharedPtr Bond::createFlow(const SharedPtr &path, int32_t flowId, un if (path) { flow->assignPath(path,now); path->address().toString(curPathStr); + path->_assignedFlowCount++; SharedPtr slave = RR->bc->getSlaveBySocket(_policyAlias, flow->assignedPath()->localSocket()); fprintf(stderr, "assigned (rx) flow %x with peer %llx to path %s on %s\n", flow->id(), _peer->_id.address().toInt(), curPathStr, slave->ifname().c_str()); } @@ -818,7 +831,7 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) ++it; ++updatedBondedPathCount; _paths[_bondedIdx[i]]->address().toString(pathStr); - fprintf(stderr, "setting i=%d, _bondedIdx[%d]=%d to bonded (%s %s)\n", i, i, _bondedIdx[i], getSlave(_paths[_bondedIdx[i]])->ifname().c_str(), pathStr); + //fprintf(stderr, "setting i=%d, _bondedIdx[%d]=%d to bonded (%s %s)\n", i, i, _bondedIdx[i], getSlave(_paths[_bondedIdx[i]])->ifname().c_str(), pathStr); } } _numBondedPaths = updatedBondedPathCount; @@ -834,8 +847,6 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) void Bond::estimatePathQuality(const int64_t now) { char pathStr[128]; - //--- - uint32_t totUserSpecifiedSlaveSpeed = 0; if (_numBondedPaths) { // Compute relative user-specified speeds of slaves for(unsigned int i=0;i<_numBondedPaths;++i) { @@ -856,17 +867,11 @@ void Bond::estimatePathQuality(const int64_t now) float pdv[ZT_MAX_PEER_NETWORK_PATHS]; float plr[ZT_MAX_PEER_NETWORK_PATHS]; float per[ZT_MAX_PEER_NETWORK_PATHS]; - float thr[ZT_MAX_PEER_NETWORK_PATHS]; - float thm[ZT_MAX_PEER_NETWORK_PATHS]; - float thv[ZT_MAX_PEER_NETWORK_PATHS]; float maxLAT = 0; float maxPDV = 0; float maxPLR = 0; float maxPER = 0; - float maxTHR = 0; - float maxTHM = 0; - float maxTHV = 0; float quality[ZT_MAX_PEER_NETWORK_PATHS]; uint8_t alloc[ZT_MAX_PEER_NETWORK_PATHS]; @@ -877,9 +882,6 @@ void Bond::estimatePathQuality(const int64_t now) memset(&pdv, 0, sizeof(pdv)); memset(&plr, 0, sizeof(plr)); memset(&per, 0, sizeof(per)); - memset(&thr, 0, sizeof(thr)); - memset(&thm, 0, sizeof(thm)); - memset(&thv, 0, sizeof(thv)); memset(&quality, 0, sizeof(quality)); memset(&alloc, 0, sizeof(alloc)); @@ -901,24 +903,6 @@ void Bond::estimatePathQuality(const int64_t now) _paths[i]->_throughputVariance = 0; } } - /* - else { - // Use estimated metrics - if (_paths[i]->throughputSamples.count()) { - // If we have samples, use them - _paths[i]->throughputMean = (uint64_t)_paths[i]->throughputSamples.mean(); - if (_paths[i]->throughputMean > 0) { - _paths[i]->throughputVarianceSamples.push((float)_paths[i]->throughputSamples.stddev() / (float)_paths[i]->throughputMean); - _paths[i]->throughputVariance = _paths[i]->throughputVarianceSamples.mean(); - } - } - else { - // No samples have been collected yet, assume best case scenario - _paths[i]->throughputMean = ZT_QOS_THR_NORM_MAX; - _paths[i]->throughputVariance = 0; - } - } - */ // Drain unacknowledged QoS records std::map::iterator it = _paths[i]->qosStatsOut.begin(); uint64_t currentLostRecords = 0; @@ -934,23 +918,16 @@ void Bond::estimatePathQuality(const int64_t now) quality[i]=0; totQuality=0; // Normalize raw observations according to sane limits and/or user specified values - lat[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_latencyMean, 0, _maxAcceptableLatency, 0, 1)); - pdv[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_latencyVariance, 0, _maxAcceptablePacketDelayVariance, 0, 1)); - plr[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_packetLossRatio, 0, _maxAcceptablePacketLossRatio, 0, 1)); - per[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_packetErrorRatio, 0, _maxAcceptablePacketErrorRatio, 0, 1)); - //thr[i] = 1.0; //Utils::normalize(_paths[i]->throughputMean, 0, ZT_QOS_THR_NORM_MAX, 0, 1); - //thm[i] = 1.0; //Utils::normalize(_paths[i]->throughputMax, 0, ZT_QOS_THM_NORM_MAX, 0, 1); - //thv[i] = 1.0; //1.0 / expf(4*Utils::normalize(_paths[i]->throughputVariance, 0, ZT_QOS_THV_NORM_MAX, 0, 1)); + lat[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_latencyMean, 0, _maxAcceptableLatency, 0, 1)); + pdv[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_latencyVariance, 0, _maxAcceptablePacketDelayVariance, 0, 1)); + plr[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_packetLossRatio, 0, _maxAcceptablePacketLossRatio, 0, 1)); + per[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_packetErrorRatio, 0, _maxAcceptablePacketErrorRatio, 0, 1)); //scp[i] = _paths[i]->ipvPref != 0 ? 1.0 : Utils::normalize(_paths[i]->ipScope(), InetAddress::IP_SCOPE_NONE, InetAddress::IP_SCOPE_PRIVATE, 0, 1); // Record bond-wide maximums to determine relative values maxLAT = lat[i] > maxLAT ? lat[i] : maxLAT; maxPDV = pdv[i] > maxPDV ? pdv[i] : maxPDV; maxPLR = plr[i] > maxPLR ? plr[i] : maxPLR; maxPER = per[i] > maxPER ? per[i] : maxPER; - //maxTHR = thr[i] > maxTHR ? thr[i] : maxTHR; - //maxTHM = thm[i] > maxTHM ? thm[i] : maxTHM; - //maxTHV = thv[i] > maxTHV ? thv[i] : maxTHV; - //fprintf(stdout, "EH %d: lat=%8.3f, ltm=%8.3f, pdv=%8.3f, plr=%5.3f, per=%5.3f, thr=%8f, thm=%5.3f, thv=%5.3f, avl=%5.3f, age=%8.2f, scp=%4d, q=%5.3f, qtot=%5.3f, ac=%d if=%s, path=%s\n", // i, lat[i], ltm[i], pdv[i], plr[i], per[i], thr[i], thm[i], thv[i], avl[i], age[i], scp[i], quality[i], totQuality, alloc[i], getSlave(_paths[i])->ifname().c_str(), pathStr); @@ -962,9 +939,6 @@ void Bond::estimatePathQuality(const int64_t now) quality[i] += ((maxPDV > 0.0f ? pdv[i] / maxPDV : 0.0f) * _qualityWeights[ZT_QOS_PDV_IDX]); quality[i] += ((maxPLR > 0.0f ? plr[i] / maxPLR : 0.0f) * _qualityWeights[ZT_QOS_PLR_IDX]); quality[i] += ((maxPER > 0.0f ? per[i] / maxPER : 0.0f) * _qualityWeights[ZT_QOS_PER_IDX]); - //quality[i] += ((maxTHR > 0.0f ? thr[i] / maxTHR : 0.0f) * _qualityWeights[ZT_QOS_THR_IDX]); - //quality[i] += ((maxTHM > 0.0f ? thm[i] / maxTHM : 0.0f) * _qualityWeights[ZT_QOS_THM_IDX]); - //quality[i] += ((maxTHV > 0.0f ? thv[i] / maxTHV : 0.0f) * _qualityWeights[ZT_QOS_THV_IDX]); //quality[i] += (scp[i] * _qualityWeights[ZT_QOS_SCP_IDX]); totQuality += quality[i]; } @@ -1007,6 +981,7 @@ void Bond::estimatePathQuality(const int64_t now) } _header=true; } + /* fprintf(stdout, "%ld, %d, %d, %d, ",((now - RR->bc->getBondStartTime())),_numBondedPaths,_totalBondUnderload, _flows.size()); for(unsigned int i=0;iifname().c_str(), pathStr, _paths[i]->_latencyMean, lat[i],pdv[i], _paths[i]->_packetLossRatio, plr[i],per[i],thr[i],thm[i],thv[i],(now - _paths[i]->lastIn()),quality[i],alloc[i], _paths[i]->_relativeByteLoad, _paths[i]->_assignedFlowCount, _paths[i]->alive(now, true), _paths[i]->eligible(now,_ackSendInterval), _paths[i]->qosStatsOut.size()); } - } - fprintf(stdout, "\n"); + }*/ + //fprintf(stdout, "\n"); } } void Bond::processBalanceTasks(const int64_t now) { - //fprintf(stderr, "processBalanceTasks\n"); char curPathStr[128]; + + // TODO: Generalize + int totalAllocation = 0; + for (int i=0;ibonded() && _paths[i]->eligible(now,_ackSendInterval)) { + totalAllocation+=_paths[i]->_allocation; + } + } + unsigned char minimumAllocationValue = 0.33 * ((float)totalAllocation / (float)_numBondedPaths); + if (_allowFlowHashing) { /** * Clean up and reset flows if necessary @@ -1067,6 +1054,32 @@ void Bond::processBalanceTasks(const int64_t now) } } } + /** + * Re-allocate flows from under-performing + * NOTE: This could be part of the above block but was kept separate for clarity. + */ + if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { + Mutex::Lock _l(_flows_m); + for (int i=0;ibonded() && _paths[i]->eligible(now,_ackSendInterval) && (_paths[i]->_allocation < minimumAllocationValue) && _paths[i]->_assignedFlowCount) { + _paths[i]->address().toString(curPathStr); + fprintf(stderr, "%d reallocating flows from under-performing path %s on %s\n", (RR->node->now() - RR->bc->getBondStartTime()), curPathStr, getSlave(_paths[i])->ifname().c_str()); + std::map >::iterator flow_it = _flows.begin(); + while (flow_it != _flows.end()) { + if (flow_it->second->assignedPath() == _paths[i]) { + if(assignFlowToBondedPath(flow_it->second, now)) { + _paths[i]->_assignedFlowCount--; + } + } + ++flow_it; + } + _paths[i]->_shouldReallocateFlows = false; + } + } + } } /** * Tasks specific to (Balance Round Robin) @@ -1091,70 +1104,47 @@ void Bond::processBalanceTasks(const int64_t now) if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { if (_allowFlowHashing) { Mutex::Lock _l(_flows_m); - /** - * Re-balance flows in proportion to slave capacity (or when eligibility changes) - */ - if ((now - _lastFlowRebalance) > ZT_FLOW_REBALANCE_INTERVAL) { + if (_flowRebalanceStrategy == ZT_MULTIPATH_FLOW_REBALANCE_STRATEGY_PASSIVE) { + // Do nothing here, this is taken care of in the more general case above. + } + if (_flowRebalanceStrategy == ZT_MULTIPATH_FLOW_REBALANCE_STRATEGY_OPPORTUNISTIC) { + // If the flow is temporarily inactive we should take this opportunity to re-assign the flow if needed. + } + if (_flowRebalanceStrategy == ZT_MULTIPATH_FLOW_REBALANCE_STRATEGY_AGGRESSIVE) { /** - * Determine "load" for bonded paths + * Return flows to the original path if it has once again become available */ - uint64_t totalBytes = 0; - for(unsigned int i=0;ibonded()) { - _paths[i]->_byteLoad = 0; - std::map >::iterator flow_it = _flows.begin(); - while (flow_it != _flows.end()) { - if (flow_it->second->assignedPath() == _paths[i]) { - _paths[i]->_byteLoad += flow_it->second->totalBytes(); - } - ++flow_it; + if ((now - _lastFlowRebalance) > ZT_FLOW_REBALANCE_INTERVAL) { + std::map >::iterator flow_it = _flows.begin(); + while (flow_it != _flows.end()) { + if (flow_it->second->_previouslyAssignedPath && flow_it->second->_previouslyAssignedPath->eligible(now, _ackSendInterval) + && (flow_it->second->_previouslyAssignedPath->_allocation >= (minimumAllocationValue * 2))) { + fprintf(stderr, "moving flow back onto its previous path assignment (based on eligibility)\n"); + (flow_it->second->_assignedPath->_assignedFlowCount)--; + flow_it->second->assignPath(flow_it->second->_previouslyAssignedPath,now); + (flow_it->second->_previouslyAssignedPath->_assignedFlowCount)++; } - totalBytes += _paths[i]->_byteLoad; + ++flow_it; } + _lastFlowRebalance = now; } /** - * Determine "affinity" for bonded path + * Return flows to the original path if it has once again become (performant) */ - //fprintf(stderr, "\n\n"); - - _totalBondUnderload = 0; -/* - for(unsigned int i=0;ibonded()) { - if (totalBytes) { - uint8_t relativeByteLoad = std::ceil(((float)_paths[i]->_byteLoad / (float)totalBytes) * (float)255); - //fprintf(stderr, "lastComputedAllocation = %d\n", _paths[i]->allocation); - //fprintf(stderr, " relativeByteLoad = %d\n", relativeByteLoad); - _paths[i]->_relativeByteLoad = relativeByteLoad; - uint8_t relativeUnderload = std::max(0, (int)_paths[i]->_allocation - (int)relativeByteLoad); - //fprintf(stderr, " relativeUnderload = %d\n", relativeUnderload); - _totalBondUnderload += relativeUnderload; - //fprintf(stderr, " _totalBondUnderload = %d\n\n", _totalBondUnderload); - //_paths[i]->affinity = (relativeUnderload > 0 ? relativeUnderload : _paths[i]->_allocation); - } - else { // set everything to base values - _totalBondUnderload = 0; - //_paths[i]->affinity = 0; + if ((now - _lastFlowRebalance) > ZT_FLOW_REBALANCE_INTERVAL) { + std::map >::iterator flow_it = _flows.begin(); + while (flow_it != _flows.end()) { + if (flow_it->second->_previouslyAssignedPath && flow_it->second->_previouslyAssignedPath->eligible(now, _ackSendInterval) + && (flow_it->second->_previouslyAssignedPath->_allocation >= (minimumAllocationValue * 2))) { + fprintf(stderr, "moving flow back onto its previous path assignment (based on performance)\n"); + (flow_it->second->_assignedPath->_assignedFlowCount)--; + flow_it->second->assignPath(flow_it->second->_previouslyAssignedPath,now); + (flow_it->second->_previouslyAssignedPath->_assignedFlowCount)++; } + ++flow_it; } + _lastFlowRebalance = now; } -*/ - //fprintf(stderr, "_totalBondUnderload=%d (end)\n\n", _totalBondUnderload); - - /** - * - */ - //fprintf(stderr, "_lastFlowRebalance\n"); - std::map >::iterator it = _flows.begin(); - while (it != _flows.end()) { - int32_t flowId = it->first; - SharedPtr flow = it->second; - if ((now - flow->_lastPathReassignment) > ZT_FLOW_MIN_REBALANCE_INTERVAL) { - //fprintf(stdout, " could move : %x\n", flowId); - } - ++it; - } - _lastFlowRebalance = now; } } else if (!_allowFlowHashing) { @@ -1440,7 +1430,7 @@ void Bond::processActiveBackupTasks(const int64_t now) if (!_abFailoverQueue.empty()) { fprintf(stderr, "%llu AB: (failure) there are (%lu) slaves in queue to choose from...\n", ((now - RR->bc->getBondStartTime())), _abFailoverQueue.size()); dequeueNextActiveBackupPath(now); - _abPath->address().toString(curPathStr); fprintf(stderr, "%llu sAB: (failure) switched to %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, getSlave(_abPath)->ifname().c_str()); + _abPath->address().toString(curPathStr); fprintf(stderr, "%llu AB: (failure) switched to %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, getSlave(_abPath)->ifname().c_str()); } else { fprintf(stderr, "%llu AB: (failure) nothing available in the slave queue, doing nothing.\n", ((now - RR->bc->getBondStartTime()))); } @@ -1515,12 +1505,16 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _bondingPolicy= policy; } + _freeRandomByte = 0; + _lastCheckUserPreferences = 0; + _lastBackgroundTaskCheck = 0; + _downDelay = 0; _upDelay = 0; _allowFlowHashing=false; _bondMonitorInterval=0; _shouldCollectPathStatistics=false; - _lastBackgroundTaskCheck=0; + // Path negotiation _allowPathNegotiation=false; @@ -1539,7 +1533,7 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _lastFlowRebalance=0; _totalBondUnderload = 0; - //_maxAcceptableLatency + _maxAcceptableLatency = 100; _maxAcceptablePacketDelayVariance = 50; _maxAcceptablePacketLossRatio = 0.10; _maxAcceptablePacketErrorRatio = 0.10; @@ -1547,17 +1541,18 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _lastFrame=0; - // TODO: Remove - _header=false; - _lastLogTS = RR->node->now(); - _lastPrintTS = RR->node->now(); + + + /* ZT_MULTIPATH_FLOW_REBALANCE_STRATEGY_PASSIVE is the most conservative strategy and is + least likely to cause unexpected behavior */ + _flowRebalanceStrategy = ZT_MULTIPATH_FLOW_REBALANCE_STRATEGY_AGGRESSIVE; /** * Paths are actively monitored to provide a real-time quality/preference-ordered rapid failover queue. */ switch (policy) { case ZT_BONDING_POLICY_ACTIVE_BACKUP: - _failoverInterval = 5000; + _failoverInterval = 500; _abSlaveSelectMethod = ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE; _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; _qualityWeights[ZT_QOS_LAT_IDX] = 0.2f; @@ -1581,7 +1576,7 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool * Paths are monitored to determine when/if one needs to be added or removed from the rotation */ case ZT_BONDING_POLICY_BALANCE_RR: - _failoverInterval = 5000; + _failoverInterval = 500; _allowFlowHashing = false; _packetsPerSlave = 1024; _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; @@ -1600,8 +1595,8 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool * path and where to place the next flow. */ case ZT_BONDING_POLICY_BALANCE_XOR: - _failoverInterval = 5000;; - _upDelay=_bondMonitorInterval*2; + _failoverInterval = 500; + _upDelay = _bondMonitorInterval * 2; _allowFlowHashing = true; _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; _qualityWeights[ZT_QOS_LAT_IDX] = 0.4f; @@ -1623,13 +1618,13 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _failoverInterval = 3000; _allowFlowHashing = true; _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; - _qualityWeights[ZT_QOS_LAT_IDX] = 0.3f; + _qualityWeights[ZT_QOS_LAT_IDX] = 0.4f; _qualityWeights[ZT_QOS_LTM_IDX] = 0.0f; - _qualityWeights[ZT_QOS_PDV_IDX] = 0.1f; - _qualityWeights[ZT_QOS_PLR_IDX] = 0.1f; - _qualityWeights[ZT_QOS_PER_IDX] = 0.1f; + _qualityWeights[ZT_QOS_PDV_IDX] = 0.4f; + _qualityWeights[ZT_QOS_PLR_IDX] = 0.2f; + _qualityWeights[ZT_QOS_PER_IDX] = 0.0f; _qualityWeights[ZT_QOS_THR_IDX] = 0.0f; - _qualityWeights[ZT_QOS_THM_IDX] = 0.4f; + _qualityWeights[ZT_QOS_THM_IDX] = 0.0f; _qualityWeights[ZT_QOS_THV_IDX] = 0.0f; _qualityWeights[ZT_QOS_SCP_IDX] = 0.0f; break; @@ -1637,6 +1632,8 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool break; } + /* If a user has specified custom parameters for this bonding policy, overlay + them onto the defaults that were previously set */ if (useTemplate) { _policyAlias = templateBond->_policyAlias; _failoverInterval = templateBond->_failoverInterval; @@ -1742,7 +1739,7 @@ void Bond::dumpInfo(const int64_t now) fprintf(stderr, "Paths (bp=%d, stats=%d, fh=%d) :\n", _policy, _shouldCollectPathStatistics, _allowFlowHashing); }*/ - if ((now - _lastPrintTS) < 1000) { + if ((now - _lastPrintTS) < 2000) { return; } _lastPrintTS = now; @@ -1856,30 +1853,7 @@ void Bond::dumpInfo(const int64_t now) currPathStr); } } - /* - if (_allowFlowHashing) { - //Mutex::Lock _l(_flows_m); - if (_flows.size()) { - fprintf(stderr, "\nFlows:\n"); - std::map >::iterator it = _flows.begin(); - while (it != _flows.end()) { - it->second->assignedPath()->address().toString(currPathStr); - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, it->second->assignedPath()->localSocket()); - fprintf(stderr, " [%4x] in=%16llu, out=%16llu, bytes=%16llu, last=%16llu, if=%8s\t\t%s\n", - it->second->id(), - it->second->bytesInPerUnitTime(), - it->second->bytesOutPerUnitTime(), - it->second->totalBytes(), - it->second->age(now), - slave->ifname().c_str(), - currPathStr); - ++it; - } - } - } - */ } - //fprintf(stderr, "\n\n\n\n\n"); } } // namespace ZeroTier \ No newline at end of file diff --git a/node/Bond.hpp b/node/Bond.hpp index e60e27a19..353ed9317 100644 --- a/node/Bond.hpp +++ b/node/Bond.hpp @@ -87,7 +87,7 @@ public: std::string policyAlias() { return _policyAlias; } /** - * Inform the bond about the path that its peer just learned about + * Inform the bond about the path that its peer (owning object) just learned about * * @param path Newly-learned Path which should now be handled by the Bond * @param now Current time @@ -434,7 +434,12 @@ public: inline void setFailoverInterval(uint32_t interval) { _failoverInterval = interval; } /** - * @param strategy The strategy that the bond uses to prob for path aliveness and quality + * @param strategy Strategy that the bond uses to re-assign protocol flows. + */ + inline void setFlowRebalanceStrategy(uint32_t strategy) { _flowRebalanceStrategy = strategy; } + + /** + * @param strategy Strategy that the bond uses to prob for path aliveness and quality */ inline void setSlaveMonitorStrategy(uint8_t strategy) { _slaveMonitorStrategy = strategy; } @@ -578,6 +583,7 @@ private: // balance-aware uint64_t _totalBondUnderload; + uint8_t _flowRebalanceStrategy; // dynamic slave monitoring uint8_t _slaveMonitorStrategy; diff --git a/node/BondController.cpp b/node/BondController.cpp index 06da41759..6b21d9998 100644 --- a/node/BondController.cpp +++ b/node/BondController.cpp @@ -11,6 +11,7 @@ */ /****/ +#include "Constants.hpp" #include "BondController.hpp" #include "Peer.hpp" @@ -23,6 +24,7 @@ BondController::BondController(const RuntimeEnvironment *renv) : RR(renv) { bondStartTime = RR->node->now(); + _defaultBondingPolicy = ZT_BONDING_POLICY_NONE; } bool BondController::slaveAllowed(std::string &policyAlias, SharedPtr slave) @@ -83,10 +85,9 @@ SharedPtr BondController::createTransportTriggeredBond(const RuntimeEnviro Bond *bond = nullptr; if (!_bonds.count(identity)) { std::string policyAlias; - int _defaultBondingPolicy = defaultBondingPolicy(); fprintf(stderr, "new bond, registering for %llx\n", identity); if (!_policyTemplateAssignments.count(identity)) { - if (defaultBondingPolicy()) { + if (_defaultBondingPolicy) { fprintf(stderr, " no assignment, using default (%d)\n", _defaultBondingPolicy); bond = new Bond(renv, _defaultBondingPolicy, peer); } diff --git a/node/Constants.hpp b/node/Constants.hpp index c27e02319..9f2cd80a5 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -341,11 +341,6 @@ */ #define ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE 32 -/** - * Number of samples to consider when processing long-term trends - */ -#define ZT_QOS_LONGTERM_SAMPLE_WIN_SIZE (ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE * 4) - /** * Max allowable time spent in any queue (in ms) */ diff --git a/node/Flow.hpp b/node/Flow.hpp index 5994a4fb2..b19fd475c 100644 --- a/node/Flow.hpp +++ b/node/Flow.hpp @@ -116,6 +116,7 @@ struct Flow int64_t _lastActivity; int64_t _lastPathReassignment; SharedPtr _assignedPath; + SharedPtr _previouslyAssignedPath; }; } // namespace ZeroTier diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 702c08090..43e36f3ce 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -221,9 +221,11 @@ bool IncomingPacket::_doACK(const RuntimeEnvironment *RR,void *tPtr,const Shared bool IncomingPacket::_doQOS_MEASUREMENT(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { SharedPtr bond = peer->bond(); + /* TODO: Fix rate gate issue if (!bond || !bond->rateGateQoS(RR->node->now())) { return true; } + */ /* Dissect incoming QoS packet. From this we can compute latency values and their variance. * The latency variance is used as a measure of "jitter". */ if (payloadLength() > ZT_QOS_MAX_PACKET_SIZE || payloadLength() < ZT_QOS_MIN_PACKET_SIZE) { diff --git a/node/Peer.cpp b/node/Peer.cpp index 1ee0c1240..30911b43c 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -55,7 +55,8 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident _remoteMultipathSupported(false), _canUseMultipath(false), _shouldCollectPathStatistics(0), - _lastComputedAggregateMeanLatency(0) + _lastComputedAggregateMeanLatency(0), + _bondingPolicy(0) { if (!myIdentity.agree(peerIdentity,_key,ZT_PEER_SECRET_KEY_LENGTH)) { throw ZT_EXCEPTION_INVALID_ARGUMENT; diff --git a/service/OneService.cpp b/service/OneService.cpp index ab8594eec..ec24f7ade 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -1621,6 +1621,7 @@ public: // Bond-specific properties newTemplateBond->setUpDelay(OSUtils::jsonInt(customPolicy["upDelay"],-1)); newTemplateBond->setDownDelay(OSUtils::jsonInt(customPolicy["downDelay"],-1)); + newTemplateBond->setFlowRebalanceStrategy(OSUtils::jsonInt(customPolicy["flowRebalanceStrategy"],(uint64_t)0)); newTemplateBond->setFailoverInterval(OSUtils::jsonInt(customPolicy["failoverInterval"],(uint64_t)0)); newTemplateBond->setPacketsPerSlave(OSUtils::jsonInt(customPolicy["packetsPerSlave"],-1)); std::string slaveMonitorStrategyStr(OSUtils::jsonString(customPolicy["slaveMonitorStrategy"],"")); From a33a494d6070c12669bb9696616d725155c01dc7 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Wed, 17 Jun 2020 14:54:13 -0700 Subject: [PATCH 096/362] Adjust terminology --- include/ZeroTierOne.h | 28 +-- node/Bond.cpp | 364 +++++++++++++++++----------------- node/Bond.hpp | 60 +++--- node/BondController.cpp | 80 ++++---- node/BondController.hpp | 34 ++-- node/Constants.hpp | 6 +- node/Node.cpp | 2 - node/Path.hpp | 20 +- node/Peer.cpp | 4 +- osdep/Binder.hpp | 14 +- osdep/{Slave.hpp => Link.hpp} | 84 ++++---- service/MULTIPATH.md | 103 +++++----- service/OneService.cpp | 83 ++++---- 13 files changed, 440 insertions(+), 442 deletions(-) rename osdep/{Slave.hpp => Link.hpp} (58%) diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index dfb520469..afa75c290 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -455,39 +455,39 @@ enum ZT_MultipathBondingPolicy }; /** - * Multipath active re-selection policy (slaveSelectMethod) + * Multipath active re-selection policy (linkSelectMethod) */ -enum ZT_MultipathSlaveSelectMethod +enum ZT_MultipathLinkSelectMethod { /** - * Primary slave regains status as active slave whenever it comes back up - * (default when slaves are explicitly specified) + * Primary link regains status as active link whenever it comes back up + * (default when links are explicitly specified) */ ZT_MULTIPATH_RESELECTION_POLICY_ALWAYS = 0, /** - * Primary slave regains status as active slave when it comes back up and - * (if) it is better than the currently-active slave. + * Primary link regains status as active link when it comes back up and + * (if) it is better than the currently-active link. */ ZT_MULTIPATH_RESELECTION_POLICY_BETTER = 1, /** - * Primary slave regains status as active slave only if the currently-active - * slave fails. + * Primary link regains status as active link only if the currently-active + * link fails. */ ZT_MULTIPATH_RESELECTION_POLICY_FAILURE = 2, /** - * The primary slave can change if a superior path is detected. + * The primary link can change if a superior path is detected. * (default if user provides no fail-over guidance) */ ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE = 3 }; /** - * Mode of multipath slave interface + * Mode of multipath link interface */ -enum ZT_MultipathSlaveMode +enum ZT_MultipathLinkMode { ZT_MULTIPATH_SLAVE_MODE_PRIMARY = 0, ZT_MULTIPATH_SLAVE_MODE_SPARE = 1 @@ -527,7 +527,7 @@ enum ZT_MultipathMonitorStrategy enum ZT_MultipathFlowRebalanceStrategy { /** - * Flows will only be re-balanced among slaves during + * Flows will only be re-balanced among links during * assignment or failover. This minimizes the possibility * of sequence reordering and is thus the default setting. */ @@ -535,13 +535,13 @@ enum ZT_MultipathFlowRebalanceStrategy /** * Flows that are active may be re-assigned to a new more - * suitable slave if it can be done without disrupting the flow. + * suitable link if it can be done without disrupting the flow. * This setting can sometimes cause sequence re-ordering. */ ZT_MULTIPATH_FLOW_REBALANCE_STRATEGY_OPPORTUNISTIC = 0, /** - * Flows will be continuously re-assigned the most suitable slave + * Flows will be continuously re-assigned the most suitable link * in order to maximize "balance". This can often cause sequence * reordering and is thus only reccomended for protocols like UDP. */ diff --git a/node/Bond.cpp b/node/Bond.cpp index 656285925..0338f5195 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -54,9 +54,9 @@ Bond::Bond(const RuntimeEnvironment *renv, SharedPtr originalBond, const S void Bond::nominatePath(const SharedPtr& path, int64_t now) { - char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "nominatePath: %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "nominatePath: %s %s\n", getLink(path)->ifname().c_str(), pathStr); Mutex::Lock _l(_paths_m); - if (!RR->bc->slaveAllowed(_policyAlias, getSlave(path))) { + if (!RR->bc->linkAllowed(_policyAlias, getLink(path))) { return; } bool alreadyPresent = false; @@ -72,7 +72,7 @@ void Bond::nominatePath(const SharedPtr& path, int64_t now) if (!_paths[i]) { fprintf(stderr, "notifyOfNewPath(): Setting path %s to idx=%d\n", pathStr, i); _paths[i] = path; - //_paths[i]->slave = RR->bc->getSlaveBySocket(_policyAlias, path->localSocket()); + //_paths[i]->link = RR->bc->getLinkBySocket(_policyAlias, path->localSocket()); _paths[i]->startTrial(now); break; } @@ -107,18 +107,18 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) */ if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR) { if (!_allowFlowHashing) { - //fprintf(stderr, "_rrPacketsSentOnCurrSlave=%d, _numBondedPaths=%d, _rrIdx=%d\n", _rrPacketsSentOnCurrSlave, _numBondedPaths, _rrIdx); - if (_packetsPerSlave == 0) { + //fprintf(stderr, "_rrPacketsSentOnCurrLink=%d, _numBondedPaths=%d, _rrIdx=%d\n", _rrPacketsSentOnCurrLink, _numBondedPaths, _rrIdx); + if (_packetsPerLink == 0) { // Randomly select a path return _paths[_bondedIdx[_freeRandomByte % _numBondedPaths]]; // TODO: Optimize } - if (_rrPacketsSentOnCurrSlave < _packetsPerSlave) { - // Continue to use this slave - ++_rrPacketsSentOnCurrSlave; + if (_rrPacketsSentOnCurrLink < _packetsPerLink) { + // Continue to use this link + ++_rrPacketsSentOnCurrLink; return _paths[_bondedIdx[_rrIdx]]; } // Reset striping counter - _rrPacketsSentOnCurrSlave = 0; + _rrPacketsSentOnCurrLink = 0; if (_numBondedPaths == 1) { _rrIdx = 0; } @@ -170,7 +170,7 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) void Bond::recordIncomingInvalidPacket(const SharedPtr& path) { - // char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordIncomingInvalidPacket() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + // char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordIncomingInvalidPacket() %s %s\n", getLink(path)->ifname().c_str(), pathStr); Mutex::Lock _l(_paths_m); for (int i=0; i& path) void Bond::recordOutgoingPacket(const SharedPtr &path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now) { - // char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordOutgoingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getSlave(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); + // char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordOutgoingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getLink(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); _freeRandomByte += (unsigned char)(packetId >> 8); // Grab entropy to use in path selection logic if (!_shouldCollectPathStatistics) { return; @@ -218,7 +218,7 @@ void Bond::recordOutgoingPacket(const SharedPtr &path, const uint64_t pack void Bond::recordIncomingPacket(const SharedPtr& path, uint64_t packetId, uint16_t payloadLength, Packet::Verb verb, int32_t flowId, int64_t now) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordIncomingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getSlave(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordIncomingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getLink(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); bool isFrame = (verb == Packet::VERB_FRAME || verb == Packet::VERB_EXT_FRAME); bool shouldRecord = (packetId & (ZT_QOS_ACK_DIVISOR - 1) && (verb != Packet::VERB_ACK) @@ -261,7 +261,7 @@ void Bond::recordIncomingPacket(const SharedPtr& path, uint64_t packetId, void Bond::receivedQoS(const SharedPtr& path, int64_t now, int count, uint64_t *rx_id, uint16_t *rx_ts) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "receivedQoS() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "receivedQoS() %s %s\n", getLink(path)->ifname().c_str(), pathStr); Mutex::Lock _l(_paths_m); // Look up egress times and compute latency values for each record std::map::iterator it; @@ -273,13 +273,13 @@ void Bond::receivedQoS(const SharedPtr& path, int64_t now, int count, uint } } path->qosRecordSize.push(count); - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "receivedQoS() on path %s %s, count=%d, successful=%d, qosStatsOut.size()=%d\n", getSlave(path)->ifname().c_str(), pathStr, count, path->aknowledgedQoSRecordCountSinceLastCheck, path->qosStatsOut.size()); + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "receivedQoS() on path %s %s, count=%d, successful=%d, qosStatsOut.size()=%d\n", getLink(path)->ifname().c_str(), pathStr, count, path->aknowledgedQoSRecordCountSinceLastCheck, path->qosStatsOut.size()); } void Bond::receivedAck(const SharedPtr& path, int64_t now, int32_t ackedBytes) { Mutex::Lock _l(_paths_m); - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "receivedAck() %s %s, (ackedBytes=%d, lastAckReceived=%lld, ackAge=%lld)\n", getSlave(path)->ifname().c_str(), pathStr, ackedBytes, path->lastAckReceived, path->ackAge(now)); + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "receivedAck() %s %s, (ackedBytes=%d, lastAckReceived=%lld, ackAge=%lld)\n", getLink(path)->ifname().c_str(), pathStr, ackedBytes, path->lastAckReceived, path->ackAge(now)); path->_lastAckReceived = now; path->_unackedBytes = (ackedBytes > path->_unackedBytes) ? 0 : path->_unackedBytes - ackedBytes; int64_t timeSinceThroughputEstimate = (now - path->_lastThroughputEstimation); @@ -300,7 +300,7 @@ void Bond::receivedAck(const SharedPtr& path, int64_t now, int32_t ackedBy int32_t Bond::generateQoSPacket(const SharedPtr& path, int64_t now, char *qosBuffer) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "generateQoSPacket() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "generateQoSPacket() %s %s\n", getLink(path)->ifname().c_str(), pathStr); int32_t len = 0; std::map::iterator it = path->qosStatsIn.begin(); int i=0; @@ -355,10 +355,10 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) //fprintf(stderr, "new entropy = %d\n", entropy); for(unsigned int i=0;ibonded()) { - SharedPtr slave = RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); _paths[i]->address().toString(curPathStr); uint8_t probabilitySegment = (_totalBondUnderload > 0) ? _paths[i]->_affinity : _paths[i]->_allocation; - //fprintf(stderr, "i=%2d, entropy=%3d, alloc=%3d, byteload=%4d, segment=%3d, _totalBondUnderload=%3d, ifname=%s, path=%20s\n", i, entropy, _paths[i]->_allocation, _paths[i]->_relativeByteLoad, probabilitySegment, _totalBondUnderload, slave->ifname().c_str(), curPathStr); + //fprintf(stderr, "i=%2d, entropy=%3d, alloc=%3d, byteload=%4d, segment=%3d, _totalBondUnderload=%3d, ifname=%s, path=%20s\n", i, entropy, _paths[i]->_allocation, _paths[i]->_relativeByteLoad, probabilitySegment, _totalBondUnderload, link->ifname().c_str(), curPathStr); if (entropy <= probabilitySegment) { idx = i; //fprintf(stderr, "\t is best path\n"); @@ -380,8 +380,8 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) } } flow->assignedPath()->address().toString(curPathStr); - SharedPtr slave = RR->bc->getSlaveBySocket(_policyAlias, flow->assignedPath()->localSocket()); - fprintf(stderr, "assigned (tx) flow %x with peer %llx to path %s on %s (idx=%d)\n", flow->id(), _peer->_id.address().toInt(), curPathStr, slave->ifname().c_str(), idx); + SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, flow->assignedPath()->localSocket()); + fprintf(stderr, "assigned (tx) flow %x with peer %llx to path %s on %s (idx=%d)\n", flow->id(), _peer->_id.address().toInt(), curPathStr, link->ifname().c_str(), idx); return true; } @@ -410,8 +410,8 @@ SharedPtr Bond::createFlow(const SharedPtr &path, int32_t flowId, un flow->assignPath(path,now); path->address().toString(curPathStr); path->_assignedFlowCount++; - SharedPtr slave = RR->bc->getSlaveBySocket(_policyAlias, flow->assignedPath()->localSocket()); - fprintf(stderr, "assigned (rx) flow %x with peer %llx to path %s on %s\n", flow->id(), _peer->_id.address().toInt(), curPathStr, slave->ifname().c_str()); + SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, flow->assignedPath()->localSocket()); + fprintf(stderr, "assigned (rx) flow %x with peer %llx to path %s on %s\n", flow->id(), _peer->_id.address().toInt(), curPathStr, link->ifname().c_str()); } /** * Add a flow when no path was provided. This means that it is an outgoing packet @@ -460,7 +460,7 @@ void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) void Bond::processIncomingPathNegotiationRequest(uint64_t now, SharedPtr &path, int16_t remoteUtility) { //fprintf(stderr, "processIncomingPathNegotiationRequest\n"); - if (_abSlaveSelectMethod != ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { + if (_abLinkSelectMethod != ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { return; } Mutex::Lock _l(_paths_m); @@ -469,18 +469,18 @@ void Bond::processIncomingPathNegotiationRequest(uint64_t now, SharedPtr & if (!_lastPathNegotiationCheck) { return; } - SharedPtr slave = RR->bc->getSlaveBySocket(_policyAlias, path->localSocket()); + SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, path->localSocket()); if (remoteUtility > _localUtility) { - fprintf(stderr, "peer suggests path, its utility (%d) is greater than ours (%d), we will switch to %s on %s (ls=%llx)\n", remoteUtility, _localUtility, pathStr, slave->ifname().c_str(), path->localSocket()); + fprintf(stderr, "peer suggests path, its utility (%d) is greater than ours (%d), we will switch to %s on %s (ls=%llx)\n", remoteUtility, _localUtility, pathStr, link->ifname().c_str(), path->localSocket()); negotiatedPath = path; } if (remoteUtility < _localUtility) { - fprintf(stderr, "peer suggests path, its utility (%d) is less than ours (%d), we will NOT switch to %s on %s (ls=%llx)\n", remoteUtility, _localUtility, pathStr, slave->ifname().c_str(), path->localSocket()); + fprintf(stderr, "peer suggests path, its utility (%d) is less than ours (%d), we will NOT switch to %s on %s (ls=%llx)\n", remoteUtility, _localUtility, pathStr, link->ifname().c_str(), path->localSocket()); } if (remoteUtility == _localUtility) { fprintf(stderr, "peer suggest path, but utility is equal, picking choice made by peer with greater identity.\n"); if (_peer->_id.address().toInt() > RR->node->identity().address().toInt()) { - fprintf(stderr, "peer identity was greater, going with their choice of %s on %s (ls=%llx)\n", pathStr, slave->ifname().c_str(), path->localSocket()); + fprintf(stderr, "peer identity was greater, going with their choice of %s on %s (ls=%llx)\n", pathStr, link->ifname().c_str(), path->localSocket()); negotiatedPath = path; } else { fprintf(stderr, "our identity was greater, no change\n"); @@ -532,8 +532,8 @@ void Bond::pathNegotiationCheck(void *tPtr, const int64_t now) ++_numSentPathNegotiationRequests; _lastSentPathNegotiationRequest = now; _paths[maxOutPathIdx]->address().toString(pathStr); - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[maxOutPathIdx]->localSocket()); - fprintf(stderr, "sending request to use %s on %s, ls=%llx, utility=%d\n", pathStr, slave->ifname().c_str(), _paths[maxOutPathIdx]->localSocket(), _localUtility); + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[maxOutPathIdx]->localSocket()); + fprintf(stderr, "sending request to use %s on %s, ls=%llx, utility=%d\n", pathStr, link->ifname().c_str(), _paths[maxOutPathIdx]->localSocket(), _localUtility); } } /** @@ -551,8 +551,8 @@ void Bond::pathNegotiationCheck(void *tPtr, const int64_t now) void Bond::sendPATH_NEGOTIATION_REQUEST(void *tPtr, const SharedPtr &path) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "sendPATH_NEGOTIATION_REQUEST() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); - if (_abSlaveSelectMethod != ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "sendPATH_NEGOTIATION_REQUEST() %s %s\n", getLink(path)->ifname().c_str(), pathStr); + if (_abLinkSelectMethod != ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { return; } Packet outp(_peer->_id.address(),RR->identity.address(),Packet::VERB_PATH_NEGOTIATION_REQUEST); @@ -566,7 +566,7 @@ void Bond::sendPATH_NEGOTIATION_REQUEST(void *tPtr, const SharedPtr &path) void Bond::sendACK(void *tPtr,const SharedPtr &path,const int64_t localSocket, const InetAddress &atAddress,int64_t now) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "sendACK() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "sendACK() %s %s\n", getLink(path)->ifname().c_str(), pathStr); Packet outp(_peer->_id.address(),RR->identity.address(),Packet::VERB_ACK); int32_t bytesToAck = 0; std::map::iterator it = path->ackStatsIn.begin(); @@ -589,7 +589,7 @@ void Bond::sendACK(void *tPtr,const SharedPtr &path,const int64_t localSoc void Bond::sendQOS_MEASUREMENT(void *tPtr,const SharedPtr &path,const int64_t localSocket, const InetAddress &atAddress,int64_t now) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "sendQOS() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "sendQOS() %s %s\n", getLink(path)->ifname().c_str(), pathStr); const int64_t _now = RR->node->now(); Packet outp(_peer->_id.address(),RR->identity.address(),Packet::VERB_QOS_MEASUREMENT); char qosData[ZT_QOS_MAX_PACKET_SIZE]; @@ -615,14 +615,14 @@ void Bond::processBackgroundTasks(void *tPtr, const int64_t now) _lastBackgroundTaskCheck = now; // Compute dynamic path monitor timer interval - if (_slaveMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC) { + if (_linkMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC) { int suggestedMonitorInterval = (now - _lastFrame) / 100; _dynamicPathMonitorInterval = std::min(ZT_PATH_HEARTBEAT_PERIOD, ((suggestedMonitorInterval > _bondMonitorInterval) ? suggestedMonitorInterval : _bondMonitorInterval)); //fprintf(stderr, "_lastFrame=%llu, suggestedMonitorInterval=%d, _dynamicPathMonitorInterval=%d\n", // (now-_lastFrame), suggestedMonitorInterval, _dynamicPathMonitorInterval); } // TODO: Clarify and generalize this logic - if (_slaveMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC) { + if (_linkMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC) { _shouldCollectPathStatistics = true; } @@ -632,11 +632,11 @@ void Bond::processBackgroundTasks(void *tPtr, const int64_t now) _shouldCollectPathStatistics = true; } if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { - if (_abSlaveSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_BETTER) { - // Required for judging suitability of primary slave after recovery + if (_abLinkSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_BETTER) { + // Required for judging suitability of primary link after recovery _shouldCollectPathStatistics = true; } - if (_abSlaveSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { + if (_abLinkSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { // Required for judging suitability of new candidate primary _shouldCollectPathStatistics = true; } @@ -696,18 +696,18 @@ void Bond::applyUserPrefs() if (!_paths[i]) { continue; } - SharedPtr sl = getSlave(_paths[i]); + SharedPtr sl = getLink(_paths[i]); if (sl) { - if (sl->monitorInterval() == 0) { // If no interval was specified for this slave, use more generic bond-wide interval + if (sl->monitorInterval() == 0) { // If no interval was specified for this link, use more generic bond-wide interval sl->setMonitorInterval(_bondMonitorInterval); } RR->bc->setMinReqPathMonitorInterval((sl->monitorInterval() < RR->bc->minReqPathMonitorInterval()) ? sl->monitorInterval() : RR->bc->minReqPathMonitorInterval()); - bool bFoundCommonSlave = false; - SharedPtr commonSlave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + bool bFoundCommonLink = false; + SharedPtr commonLink =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); for(unsigned int j=0;jbc->getSlaveBySocket(_policyAlias, _paths[j]->localSocket()) == commonSlave) { - bFoundCommonSlave = true; + if (RR->bc->getLinkBySocket(_policyAlias, _paths[j]->localSocket()) == commonLink) { + bFoundCommonLink = true; } } } @@ -717,7 +717,7 @@ void Bond::applyUserPrefs() _paths[i]->_ipvPref = sl->ipvPref(); _paths[i]->_mode = sl->mode(); _paths[i]->_enabled = sl->enabled(); - _paths[i]->_onlyPathOnSlave = !bFoundCommonSlave; + _paths[i]->_onlyPathOnLink = !bFoundCommonLink; } } if (_peer) { @@ -739,11 +739,11 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) } bool currEligibility = _paths[i]->eligible(now,_ackSendInterval); //_paths[i]->address().toString(pathStr); - //fprintf(stderr, "\n\n%ld path eligibility (for %s, %s):\n", (RR->node->now() - RR->bc->getBondStartTime()), getSlave(_paths[i])->ifname().c_str(), pathStr); + //fprintf(stderr, "\n\n%ld path eligibility (for %s, %s):\n", (RR->node->now() - RR->bc->getBondStartTime()), getLink(_paths[i])->ifname().c_str(), pathStr); //_paths[i]->printEligible(now,_ackSendInterval); if (currEligibility != _paths[i]->_lastEligibilityState) { _paths[i]->address().toString(pathStr); - //fprintf(stderr, "\n\n%ld path eligibility (for %s, %s) has changed (from %d to %d)\n", (RR->node->now() - RR->bc->getBondStartTime()), getSlave(_paths[i])->ifname().c_str(), pathStr, _paths[i]->lastCheckedEligibility, _paths[i]->eligible(now,_ackSendInterval)); + //fprintf(stderr, "\n\n%ld path eligibility (for %s, %s) has changed (from %d to %d)\n", (RR->node->now() - RR->bc->getBondStartTime()), getLink(_paths[i])->ifname().c_str(), pathStr, _paths[i]->lastCheckedEligibility, _paths[i]->eligible(now,_ackSendInterval)); if (currEligibility) { rebuildBond = true; } @@ -766,7 +766,7 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) } /** * Curate the set of paths that are part of the bond proper. Selects a single path - * per logical slave according to eligibility and user-specified constraints. + * per logical link according to eligibility and user-specified constraints. */ if ((_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR) || (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR) @@ -777,68 +777,68 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) // TODO: Optimize if (rebuildBond) { int updatedBondedPathCount = 0; - std::map,int> slaveMap; + std::map,int> linkMap; for (int i=0;iallowed() && (_paths[i]->eligible(now,_ackSendInterval) || !_numBondedPaths)) { - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); - if (!slaveMap.count(slave)) { - slaveMap[slave] = i; + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); + if (!linkMap.count(link)) { + linkMap[link] = i; } else { bool overriden = false; _paths[i]->address().toString(pathStr); - //fprintf(stderr, " slave representative path already exists! (%s %s)\n", getSlave(_paths[i])->ifname().c_str(), pathStr); - if (_paths[i]->preferred() && !_paths[slaveMap[slave]]->preferred()) { + //fprintf(stderr, " link representative path already exists! (%s %s)\n", getLink(_paths[i])->ifname().c_str(), pathStr); + if (_paths[i]->preferred() && !_paths[linkMap[link]]->preferred()) { // Override previous choice if preferred //fprintf(stderr, "overriding since its preferred!\n"); - if (_paths[slaveMap[slave]]->_assignedFlowCount) { - _paths[slaveMap[slave]]->_deprecated = true; + if (_paths[linkMap[link]]->_assignedFlowCount) { + _paths[linkMap[link]]->_deprecated = true; } else { - _paths[slaveMap[slave]]->_deprecated = true; - _paths[slaveMap[slave]]->setBonded(false); + _paths[linkMap[link]]->_deprecated = true; + _paths[linkMap[link]]->setBonded(false); } - slaveMap[slave] = i; + linkMap[link] = i; overriden = true; } - if ((_paths[i]->preferred() && _paths[slaveMap[slave]]->preferred()) - || (!_paths[i]->preferred() && !_paths[slaveMap[slave]]->preferred())) { - if (_paths[i]->preferenceRank() > _paths[slaveMap[slave]]->preferenceRank()) { + if ((_paths[i]->preferred() && _paths[linkMap[link]]->preferred()) + || (!_paths[i]->preferred() && !_paths[linkMap[link]]->preferred())) { + if (_paths[i]->preferenceRank() > _paths[linkMap[link]]->preferenceRank()) { // Override if higher preference //fprintf(stderr, "overriding according to preference preferenceRank!\n"); - if (_paths[slaveMap[slave]]->_assignedFlowCount) { - _paths[slaveMap[slave]]->_deprecated = true; + if (_paths[linkMap[link]]->_assignedFlowCount) { + _paths[linkMap[link]]->_deprecated = true; } else { - _paths[slaveMap[slave]]->_deprecated = true; - _paths[slaveMap[slave]]->setBonded(false); + _paths[linkMap[link]]->_deprecated = true; + _paths[linkMap[link]]->setBonded(false); } - slaveMap[slave] = i; + linkMap[link] = i; } } } } } - std::map,int>::iterator it = slaveMap.begin(); + std::map,int>::iterator it = linkMap.begin(); for (int i=0; isecond; _paths[_bondedIdx[i]]->setBonded(true); ++it; ++updatedBondedPathCount; _paths[_bondedIdx[i]]->address().toString(pathStr); - //fprintf(stderr, "setting i=%d, _bondedIdx[%d]=%d to bonded (%s %s)\n", i, i, _bondedIdx[i], getSlave(_paths[_bondedIdx[i]])->ifname().c_str(), pathStr); + //fprintf(stderr, "setting i=%d, _bondedIdx[%d]=%d to bonded (%s %s)\n", i, i, _bondedIdx[i], getLink(_paths[_bondedIdx[i]])->ifname().c_str(), pathStr); } } _numBondedPaths = updatedBondedPathCount; if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR) { // Cause a RR reset since the currently used index might no longer be valid - _rrPacketsSentOnCurrSlave = _packetsPerSlave; + _rrPacketsSentOnCurrLink = _packetsPerLink; } } } @@ -847,18 +847,18 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) void Bond::estimatePathQuality(const int64_t now) { char pathStr[128]; - uint32_t totUserSpecifiedSlaveSpeed = 0; - if (_numBondedPaths) { // Compute relative user-specified speeds of slaves + uint32_t totUserSpecifiedLinkSpeed = 0; + if (_numBondedPaths) { // Compute relative user-specified speeds of links for(unsigned int i=0;i<_numBondedPaths;++i) { - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); if (_paths[i] && _paths[i]->allowed()) { - totUserSpecifiedSlaveSpeed += slave->speed(); + totUserSpecifiedLinkSpeed += link->speed(); } } for(unsigned int i=0;i<_numBondedPaths;++i) { - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); if (_paths[i] && _paths[i]->allowed()) { - slave->setRelativeSpeed(round( ((float)slave->speed() / (float)totUserSpecifiedSlaveSpeed) * 255)); + link->setRelativeSpeed(round( ((float)link->speed() / (float)totUserSpecifiedLinkSpeed) * 255)); } } } @@ -895,11 +895,11 @@ void Bond::estimatePathQuality(const int64_t now) _paths[i]->_latencyVariance = _paths[i]->latencySamples.stddev(); _paths[i]->_packetErrorRatio = 1.0 - (_paths[i]->packetValiditySamples.count() ? _paths[i]->packetValiditySamples.mean() : 1.0); - if (userHasSpecifiedSlaveSpeeds()) { + if (userHasSpecifiedLinkSpeeds()) { // Use user-reported metrics - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); - if (slave) { - _paths[i]->_throughputMean = slave->speed(); + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); + if (link) { + _paths[i]->_throughputMean = link->speed(); _paths[i]->_throughputVariance = 0; } } @@ -929,7 +929,7 @@ void Bond::estimatePathQuality(const int64_t now) maxPLR = plr[i] > maxPLR ? plr[i] : maxPLR; maxPER = per[i] > maxPER ? per[i] : maxPER; //fprintf(stdout, "EH %d: lat=%8.3f, ltm=%8.3f, pdv=%8.3f, plr=%5.3f, per=%5.3f, thr=%8f, thm=%5.3f, thv=%5.3f, avl=%5.3f, age=%8.2f, scp=%4d, q=%5.3f, qtot=%5.3f, ac=%d if=%s, path=%s\n", - // i, lat[i], ltm[i], pdv[i], plr[i], per[i], thr[i], thm[i], thv[i], avl[i], age[i], scp[i], quality[i], totQuality, alloc[i], getSlave(_paths[i])->ifname().c_str(), pathStr); + // i, lat[i], ltm[i], pdv[i], plr[i], per[i], thr[i], thm[i], thv[i], avl[i], age[i], scp[i], quality[i], totQuality, alloc[i], getLink(_paths[i])->ifname().c_str(), pathStr); } // Convert metrics to relative quantities and apply contribution weights @@ -962,7 +962,7 @@ void Bond::estimatePathQuality(const int64_t now) //fprintf(stderr, "%lu FIN [%d/%d]: pmi=%5d, lat=%4.3f, ltm=%4.3f, pdv=%4.3f, plr=%4.3f, per=%4.3f, thr=%4.3f, thm=%4.3f, thv=%4.3f, age=%4.3f, scp=%4d, q=%4.3f, qtot=%4.3f, ac=%4d, asf=%3d, if=%s, path=%20s, bond=%d, qosout=%d, plrraw=%d\n", // ((now - RR->bc->getBondStartTime())), i, _numBondedPaths, _paths[i]->monitorInterval, // lat[i], ltm[i], pdv[i], plr[i], per[i], thr[i], thm[i], thv[i], age[i], scp[i], - // quality[i], totQuality, alloc[i], _paths[i]->assignedFlowCount, getSlave(_paths[i])->ifname().c_str(), pathStr, _paths[i]->bonded(), _paths[i]->qosStatsOut.size(), _paths[i]->packetLossRatio); + // quality[i], totQuality, alloc[i], _paths[i]->assignedFlowCount, getLink(_paths[i])->ifname().c_str(), pathStr, _paths[i]->bonded(), _paths[i]->qosStatsOut.size(), _paths[i]->packetLossRatio); } } if (numPlottablePaths < 2) { @@ -973,7 +973,7 @@ void Bond::estimatePathQuality(const int64_t now) for(unsigned int i=0;iaddress().toString(pathStr); - std::string label = std::string((pathStr)) + " " + getSlave(_paths[i])->ifname(); + std::string label = std::string((pathStr)) + " " + getLink(_paths[i])->ifname(); for (int i=0; i<19; ++i) { fprintf(stdout, "%s, ", label.c_str()); } @@ -987,7 +987,7 @@ void Bond::estimatePathQuality(const int64_t now) if (_paths[i]) { _paths[i]->address().toString(pathStr); fprintf(stdout, "%s, %s, %8.3f, %8.3f, %8.3f, %5.3f, %5.3f, %5.3f, %8f, %5.3f, %5.3f, %d, %5.3f, %d, %d, %d, %d, %d, %d, ", - getSlave(_paths[i])->ifname().c_str(), pathStr, _paths[i]->_latencyMean, lat[i],pdv[i], _paths[i]->_packetLossRatio, plr[i],per[i],thr[i],thm[i],thv[i],(now - _paths[i]->lastIn()),quality[i],alloc[i], + getLink(_paths[i])->ifname().c_str(), pathStr, _paths[i]->_latencyMean, lat[i],pdv[i], _paths[i]->_packetLossRatio, plr[i],per[i],thr[i],thm[i],thv[i],(now - _paths[i]->lastIn()),quality[i],alloc[i], _paths[i]->_relativeByteLoad, _paths[i]->_assignedFlowCount, _paths[i]->alive(now, true), _paths[i]->eligible(now,_ackSendInterval), _paths[i]->qosStatsOut.size()); } }*/ @@ -1040,7 +1040,7 @@ void Bond::processBalanceTasks(const int64_t now) } if (!_paths[i]->eligible(now,_ackSendInterval) && _paths[i]->_shouldReallocateFlows) { _paths[i]->address().toString(curPathStr); - fprintf(stderr, "%d reallocating flows from dead path %s on %s\n", (RR->node->now() - RR->bc->getBondStartTime()), curPathStr, getSlave(_paths[i])->ifname().c_str()); + fprintf(stderr, "%d reallocating flows from dead path %s on %s\n", (RR->node->now() - RR->bc->getBondStartTime()), curPathStr, getLink(_paths[i])->ifname().c_str()); std::map >::iterator flow_it = _flows.begin(); while (flow_it != _flows.end()) { if (flow_it->second->assignedPath() == _paths[i]) { @@ -1066,7 +1066,7 @@ void Bond::processBalanceTasks(const int64_t now) } if (_paths[i] && _paths[i]->bonded() && _paths[i]->eligible(now,_ackSendInterval) && (_paths[i]->_allocation < minimumAllocationValue) && _paths[i]->_assignedFlowCount) { _paths[i]->address().toString(curPathStr); - fprintf(stderr, "%d reallocating flows from under-performing path %s on %s\n", (RR->node->now() - RR->bc->getBondStartTime()), curPathStr, getSlave(_paths[i])->ifname().c_str()); + fprintf(stderr, "%d reallocating flows from under-performing path %s on %s\n", (RR->node->now() - RR->bc->getBondStartTime()), curPathStr, getLink(_paths[i])->ifname().c_str()); std::map >::iterator flow_it = _flows.begin(); while (flow_it != _flows.end()) { if (flow_it->second->assignedPath() == _paths[i]) { @@ -1086,7 +1086,7 @@ void Bond::processBalanceTasks(const int64_t now) */ if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR) { if (_allowFlowHashing) { - // TODO: Should ideally failover from (idx) to a random slave, this is so that (idx+1) isn't overloaded + // TODO: Should ideally failover from (idx) to a random link, this is so that (idx+1) isn't overloaded } else if (!_allowFlowHashing) { // Nothing @@ -1176,29 +1176,29 @@ void Bond::processActiveBackupTasks(const int64_t now) SharedPtr prevActiveBackupPath = _abPath; SharedPtr nonPreferredPath; - bool bFoundPrimarySlave = false; + bool bFoundPrimaryLink = false; /** - * Select initial "active" active-backup slave + * Select initial "active" active-backup link */ if (!_abPath) { fprintf(stderr, "%llu no active backup path yet...\n", ((now - RR->bc->getBondStartTime()))); /** * [Automatic mode] - * The user has not explicitly specified slaves or their failover schedule, + * The user has not explicitly specified links or their failover schedule, * the bonding policy will now select the first eligible path and set it as * its active backup path, if a substantially better path is detected the bonding * policy will assign it as the new active backup path. If the path fails it will * simply find the next eligible path. */ - if (!userHasSpecifiedSlaves()) { - fprintf(stderr, "%llu AB: (auto) user did not specify any slaves. waiting until we know more\n", ((now - RR->bc->getBondStartTime()))); + if (!userHasSpecifiedLinks()) { + fprintf(stderr, "%llu AB: (auto) user did not specify any links. waiting until we know more\n", ((now - RR->bc->getBondStartTime()))); for (int i=0; ieligible(now,_ackSendInterval)) { _paths[i]->address().toString(curPathStr); - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); - if (slave) { - fprintf(stderr, "%llu AB: (initial) [%d] found eligible path %s on: %s\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, slave->ifname().c_str()); + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); + if (link) { + fprintf(stderr, "%llu AB: (initial) [%d] found eligible path %s on: %s\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, link->ifname().c_str()); } _abPath = _paths[i]; break; @@ -1207,57 +1207,57 @@ void Bond::processActiveBackupTasks(const int64_t now) } /** * [Manual mode] - * The user has specified slaves or failover rules that the bonding policy should adhere to. + * The user has specified links or failover rules that the bonding policy should adhere to. */ - else if (userHasSpecifiedSlaves()) { - fprintf(stderr, "%llu AB: (manual) no active backup slave, checking local.conf\n", ((now - RR->bc->getBondStartTime()))); - if (userHasSpecifiedPrimarySlave()) { - fprintf(stderr, "%llu AB: (manual) user has specified primary slave, looking for it.\n", ((now - RR->bc->getBondStartTime()))); + else if (userHasSpecifiedLinks()) { + fprintf(stderr, "%llu AB: (manual) no active backup link, checking local.conf\n", ((now - RR->bc->getBondStartTime()))); + if (userHasSpecifiedPrimaryLink()) { + fprintf(stderr, "%llu AB: (manual) user has specified primary link, looking for it.\n", ((now - RR->bc->getBondStartTime()))); for (int i=0; i slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); - if (_paths[i]->eligible(now,_ackSendInterval) && slave->primary()) { + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); + if (_paths[i]->eligible(now,_ackSendInterval) && link->primary()) { if (!_paths[i]->preferred()) { _paths[i]->address().toString(curPathStr); - fprintf(stderr, "%llu AB: (initial) [%d] found path on primary slave, taking note in case we don't find a preferred path\n", ((now - RR->bc->getBondStartTime())), i); + fprintf(stderr, "%llu AB: (initial) [%d] found path on primary link, taking note in case we don't find a preferred path\n", ((now - RR->bc->getBondStartTime())), i); nonPreferredPath = _paths[i]; - bFoundPrimarySlave = true; + bFoundPrimaryLink = true; } if (_paths[i]->preferred()) { _abPath = _paths[i]; _abPath->address().toString(curPathStr); - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); - if (slave) { - fprintf(stderr, "%llu AB: (initial) [%d] found preferred path %s on primary slave: %s\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, slave->ifname().c_str()); + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); + if (link) { + fprintf(stderr, "%llu AB: (initial) [%d] found preferred path %s on primary link: %s\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, link->ifname().c_str()); } - bFoundPrimarySlave = true; + bFoundPrimaryLink = true; break; } } } if (_abPath) { _abPath->address().toString(curPathStr); - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _abPath->localSocket()); - if (slave) { - fprintf(stderr, "%llu AB: (initial) found preferred primary path: %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, slave->ifname().c_str()); + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _abPath->localSocket()); + if (link) { + fprintf(stderr, "%llu AB: (initial) found preferred primary path: %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, link->ifname().c_str()); } } else { - if (bFoundPrimarySlave && nonPreferredPath) { + if (bFoundPrimaryLink && nonPreferredPath) { fprintf(stderr, "%llu AB: (initial) found a non-preferred primary path\n", ((now - RR->bc->getBondStartTime()))); _abPath = nonPreferredPath; } } if (!_abPath) { - fprintf(stderr, "%llu AB: (initial) designated primary slave is not yet ready\n", ((now - RR->bc->getBondStartTime()))); + fprintf(stderr, "%llu AB: (initial) designated primary link is not yet ready\n", ((now - RR->bc->getBondStartTime()))); // TODO: Should fail-over to specified backup or just wait? } } - else if (!userHasSpecifiedPrimarySlave()) { + else if (!userHasSpecifiedPrimaryLink()) { int _abIdx = ZT_MAX_PEER_NETWORK_PATHS; - fprintf(stderr, "%llu AB: (initial) user did not specify primary slave, just picking something\n", ((now - RR->bc->getBondStartTime()))); + fprintf(stderr, "%llu AB: (initial) user did not specify primary link, just picking something\n", ((now - RR->bc->getBondStartTime()))); for (int i=0; ieligible(now,_ackSendInterval)) { _abIdx = i; @@ -1269,9 +1269,9 @@ void Bond::processActiveBackupTasks(const int64_t now) } else { _abPath = _paths[_abIdx]; - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _abPath->localSocket()); - if (slave) { - fprintf(stderr, "%llu AB: (initial) selected non-primary slave idx=%d, %s on %s\n", ((now - RR->bc->getBondStartTime())), _abIdx, pathStr, slave->ifname().c_str()); + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _abPath->localSocket()); + if (link) { + fprintf(stderr, "%llu AB: (initial) selected non-primary link idx=%d, %s on %s\n", ((now - RR->bc->getBondStartTime())), _abIdx, pathStr, link->ifname().c_str()); } } } @@ -1281,14 +1281,14 @@ void Bond::processActiveBackupTasks(const int64_t now) * Update and maintain the active-backup failover queue */ if (_abPath) { - // Don't worry about the failover queue until we have an active slave - // Remove ineligible paths from the failover slave queue + // Don't worry about the failover queue until we have an active link + // Remove ineligible paths from the failover link queue for (std::list >::iterator it(_abFailoverQueue.begin()); it!=_abFailoverQueue.end();) { if ((*it) && !(*it)->eligible(now,_ackSendInterval)) { (*it)->address().toString(curPathStr); - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, (*it)->localSocket()); - if (slave) { - fprintf(stderr, "%llu AB: (fq) %s on %s is now ineligible, removing from failover queue\n", ((now - RR->bc->getBondStartTime())), curPathStr, slave->ifname().c_str()); + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, (*it)->localSocket()); + if (link) { + fprintf(stderr, "%llu AB: (fq) %s on %s is now ineligible, removing from failover queue\n", ((now - RR->bc->getBondStartTime())), curPathStr, link->ifname().c_str()); } it = _abFailoverQueue.erase(it); } else { @@ -1313,7 +1313,7 @@ void Bond::processActiveBackupTasks(const int64_t now) if (!_paths[i] || !_paths[i]->allowed() || !_paths[i]->eligible(now,_ackSendInterval)) { continue; } - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); _paths[i]->address().toString(pathStr); int failoverScoreHandicap = _paths[i]->_failoverScore; @@ -1322,8 +1322,8 @@ void Bond::processActiveBackupTasks(const int64_t now) failoverScoreHandicap += ZT_MULTIPATH_FAILOVER_HANDICAP_PREFERRED; //fprintf(stderr, "%s on %s ----> %d for preferred\n", pathStr, _paths[i]->ifname().c_str(), failoverScoreHandicap); } - if (slave->primary()) { - // If using "optimize" primary reselect mode, ignore user slave designations + if (link->primary()) { + // If using "optimize" primary reselect mode, ignore user link designations failoverScoreHandicap += ZT_MULTIPATH_FAILOVER_HANDICAP_PRIMARY; //fprintf(stderr, "%s on %s ----> %d for primary\n", pathStr, _paths[i]->ifname().c_str(), failoverScoreHandicap); } @@ -1333,17 +1333,17 @@ void Bond::processActiveBackupTasks(const int64_t now) _paths[i]->_failoverScore = newHandicap; //fprintf(stderr, "%s on %s ----> %d for allocation\n", pathStr, _paths[i]->ifname().c_str(), newHandicap); } - SharedPtr failoverSlave; - if (slave->failoverToSlave().length()) { - failoverSlave = RR->bc->getSlaveByName(_policyAlias, slave->failoverToSlave()); + SharedPtr failoverLink; + if (link->failoverToLink().length()) { + failoverLink = RR->bc->getLinkByName(_policyAlias, link->failoverToLink()); } - if (failoverSlave) { + if (failoverLink) { for (int j=0; jaddress().toString(pathStr); int inheritedHandicap = failoverScoreHandicap - 10; int newHandicap = _paths[j]->_failoverScore > inheritedHandicap ? _paths[j]->_failoverScore : inheritedHandicap; - //fprintf(stderr, "\thanding down %s on %s ----> %d\n", pathStr, getSlave(_paths[j])->ifname().c_str(), newHandicap); + //fprintf(stderr, "\thanding down %s on %s ----> %d\n", pathStr, getLink(_paths[j])->ifname().c_str(), newHandicap); if (!_paths[j]->preferred()) { newHandicap--; } @@ -1360,7 +1360,7 @@ void Bond::processActiveBackupTasks(const int64_t now) } if (!bFoundPathInQueue) { _paths[i]->address().toString(curPathStr); - fprintf(stderr, "%llu AB: (fq) [%d] added %s on %s to queue\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, getSlave(_paths[i])->ifname().c_str()); + fprintf(stderr, "%llu AB: (fq) [%d] added %s on %s to queue\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, getLink(_paths[i])->ifname().c_str()); _abFailoverQueue.push_front(_paths[i]); } } @@ -1385,8 +1385,8 @@ void Bond::processActiveBackupTasks(const int64_t now) if (!_paths[i]->eligible(now,includeRefractoryPeriod)) { failoverScoreHandicap = -10000; } - if (getSlave(_paths[i])->primary() && _abSlaveSelectMethod != ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { - // If using "optimize" primary reselect mode, ignore user slave designations + if (getLink(_paths[i])->primary() && _abLinkSelectMethod != ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { + // If using "optimize" primary reselect mode, ignore user link designations failoverScoreHandicap = ZT_MULTIPATH_FAILOVER_HANDICAP_PRIMARY; } if (_paths[i].ptr() == negotiatedPath.ptr()) { @@ -1405,7 +1405,7 @@ void Bond::processActiveBackupTasks(const int64_t now) } if (!bFoundPathInQueue) { _paths[i]->address().toString(curPathStr); - fprintf(stderr, "%llu AB: (fq) [%d] added %s on %s to queue\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, getSlave(_paths[i])->ifname().c_str()); + fprintf(stderr, "%llu AB: (fq) [%d] added %s on %s to queue\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, getLink(_paths[i])->ifname().c_str()); _abFailoverQueue.push_front(_paths[i]); } } @@ -1428,11 +1428,11 @@ void Bond::processActiveBackupTasks(const int64_t now) if (_abPath && !_abPath->eligible(now,_ackSendInterval)) { // Implicit ZT_MULTIPATH_RESELECTION_POLICY_FAILURE _abPath->address().toString(curPathStr); fprintf(stderr, "%llu AB: (failure) failover event!, active backup path (%s) is no-longer eligible\n", ((now - RR->bc->getBondStartTime())), curPathStr); if (!_abFailoverQueue.empty()) { - fprintf(stderr, "%llu AB: (failure) there are (%lu) slaves in queue to choose from...\n", ((now - RR->bc->getBondStartTime())), _abFailoverQueue.size()); + fprintf(stderr, "%llu AB: (failure) there are (%lu) links in queue to choose from...\n", ((now - RR->bc->getBondStartTime())), _abFailoverQueue.size()); dequeueNextActiveBackupPath(now); - _abPath->address().toString(curPathStr); fprintf(stderr, "%llu AB: (failure) switched to %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, getSlave(_abPath)->ifname().c_str()); + _abPath->address().toString(curPathStr); fprintf(stderr, "%llu AB: (failure) switched to %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, getLink(_abPath)->ifname().c_str()); } else { - fprintf(stderr, "%llu AB: (failure) nothing available in the slave queue, doing nothing.\n", ((now - RR->bc->getBondStartTime()))); + fprintf(stderr, "%llu AB: (failure) nothing available in the link queue, doing nothing.\n", ((now - RR->bc->getBondStartTime()))); } } /** @@ -1441,38 +1441,38 @@ void Bond::processActiveBackupTasks(const int64_t now) if (prevActiveBackupPath != _abPath) { _lastActiveBackupPathChange = now; } - if (_abSlaveSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_ALWAYS) { - if (_abPath && !getSlave(_abPath)->primary() - && getSlave(_abFailoverQueue.front())->primary()) { + if (_abLinkSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_ALWAYS) { + if (_abPath && !getLink(_abPath)->primary() + && getLink(_abFailoverQueue.front())->primary()) { fprintf(stderr, "%llu AB: (always) switching to available primary\n", ((now - RR->bc->getBondStartTime()))); dequeueNextActiveBackupPath(now); } } - if (_abSlaveSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_BETTER) { - if (_abPath && !getSlave(_abPath)->primary()) { - fprintf(stderr, "%llu AB: (better) active backup has switched to \"better\" primary slave according to re-select policy.\n", ((now - RR->bc->getBondStartTime()))); - if (getSlave(_abFailoverQueue.front())->primary() + if (_abLinkSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_BETTER) { + if (_abPath && !getLink(_abPath)->primary()) { + fprintf(stderr, "%llu AB: (better) active backup has switched to \"better\" primary link according to re-select policy.\n", ((now - RR->bc->getBondStartTime()))); + if (getLink(_abFailoverQueue.front())->primary() && (_abFailoverQueue.front()->_failoverScore > _abPath->_failoverScore)) { dequeueNextActiveBackupPath(now); fprintf(stderr, "%llu AB: (better) switched back to user-defined primary\n", ((now - RR->bc->getBondStartTime()))); } } } - if (_abSlaveSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE && !_abFailoverQueue.empty()) { + if (_abLinkSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE && !_abFailoverQueue.empty()) { /** * Implement link negotiation that was previously-decided */ if (_abFailoverQueue.front()->_negotiated) { dequeueNextActiveBackupPath(now); _abPath->address().toString(prevPathStr); - fprintf(stderr, "%llu AB: (optimize) switched to negotiated path %s on %s\n", ((now - RR->bc->getBondStartTime())), prevPathStr, getSlave(_abPath)->ifname().c_str()); + fprintf(stderr, "%llu AB: (optimize) switched to negotiated path %s on %s\n", ((now - RR->bc->getBondStartTime())), prevPathStr, getLink(_abPath)->ifname().c_str()); _lastPathNegotiationCheck = now; } else { // Try to find a better path and automatically switch to it -- not too often, though. if ((now - _lastActiveBackupPathChange) > ZT_MULTIPATH_MIN_ACTIVE_BACKUP_AUTOFLOP_INTERVAL) { if (!_abFailoverQueue.empty()) { - //fprintf(stderr, "AB: (optimize) there are (%d) slaves in queue to choose from...\n", _abFailoverQueue.size()); + //fprintf(stderr, "AB: (optimize) there are (%d) links in queue to choose from...\n", _abFailoverQueue.size()); int newFScore = _abFailoverQueue.front()->_failoverScore; int prevFScore = _abPath->_failoverScore; // Establish a minimum switch threshold to prevent flapping @@ -1483,7 +1483,7 @@ void Bond::processActiveBackupTasks(const int64_t now) _abPath->address().toString(prevPathStr); dequeueNextActiveBackupPath(now); _abPath->address().toString(curPathStr); - fprintf(stderr, "%llu AB: (optimize) switched from %s on %s (fs=%d) to %s on %s (fs=%d)\n", ((now - RR->bc->getBondStartTime())), prevPathStr, getSlave(oldPath)->ifname().c_str(), prevFScore, curPathStr, getSlave(_abPath)->ifname().c_str(), newFScore); + fprintf(stderr, "%llu AB: (optimize) switched from %s on %s (fs=%d) to %s on %s (fs=%d)\n", ((now - RR->bc->getBondStartTime())), prevPathStr, getLink(oldPath)->ifname().c_str(), prevFScore, curPathStr, getLink(_abPath)->ifname().c_str(), newFScore); } } } @@ -1527,7 +1527,7 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _lastFlowExpirationCheck=0; _numBondedPaths=0; - _rrPacketsSentOnCurrSlave=0; + _rrPacketsSentOnCurrLink=0; _rrIdx=0; _lastFlowRebalance=0; @@ -1537,7 +1537,7 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _maxAcceptablePacketDelayVariance = 50; _maxAcceptablePacketLossRatio = 0.10; _maxAcceptablePacketErrorRatio = 0.10; - _userHasSpecifiedSlaveSpeeds=0; + _userHasSpecifiedLinkSpeeds=0; _lastFrame=0; @@ -1553,8 +1553,8 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool switch (policy) { case ZT_BONDING_POLICY_ACTIVE_BACKUP: _failoverInterval = 500; - _abSlaveSelectMethod = ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE; - _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; + _abLinkSelectMethod = ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE; + _linkMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; _qualityWeights[ZT_QOS_LAT_IDX] = 0.2f; _qualityWeights[ZT_QOS_LTM_IDX] = 0.0f; _qualityWeights[ZT_QOS_PDV_IDX] = 0.2f; @@ -1578,8 +1578,8 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool case ZT_BONDING_POLICY_BALANCE_RR: _failoverInterval = 500; _allowFlowHashing = false; - _packetsPerSlave = 1024; - _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; + _packetsPerLink = 1024; + _linkMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; _qualityWeights[ZT_QOS_LAT_IDX] = 0.4f; _qualityWeights[ZT_QOS_LTM_IDX] = 0.0f; _qualityWeights[ZT_QOS_PDV_IDX] = 0.2f; @@ -1598,7 +1598,7 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _failoverInterval = 500; _upDelay = _bondMonitorInterval * 2; _allowFlowHashing = true; - _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; + _linkMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; _qualityWeights[ZT_QOS_LAT_IDX] = 0.4f; _qualityWeights[ZT_QOS_LTM_IDX] = 0.0f; _qualityWeights[ZT_QOS_PDV_IDX] = 0.2f; @@ -1617,7 +1617,7 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool case ZT_BONDING_POLICY_BALANCE_AWARE: _failoverInterval = 3000; _allowFlowHashing = true; - _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; + _linkMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; _qualityWeights[ZT_QOS_LAT_IDX] = 0.4f; _qualityWeights[ZT_QOS_LTM_IDX] = 0.0f; _qualityWeights[ZT_QOS_PDV_IDX] = 0.4f; @@ -1641,7 +1641,7 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _upDelay = templateBond->_upDelay; fprintf(stderr, "TIMERS: strat=%d, fi= %d, bmi= %d, qos= %d, ack= %d, estimateInt= %d, refractory= %d, ud= %d, dd= %d\n", - _slaveMonitorStrategy, + _linkMonitorStrategy, _failoverInterval, _bondMonitorInterval, _qosSendInterval, @@ -1651,11 +1651,11 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _upDelay, _downDelay); - if (templateBond->_slaveMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_PASSIVE + if (templateBond->_linkMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_PASSIVE && templateBond->_failoverInterval != 0) { fprintf(stderr, "warning: passive path monitoring was specified, this will prevent failovers from happening in a timely manner.\n"); } - _abSlaveSelectMethod = templateBond->_abSlaveSelectMethod; + _abLinkSelectMethod = templateBond->_abLinkSelectMethod; memcpy(_qualityWeights, templateBond->_qualityWeights, ZT_QOS_WEIGHT_SIZE * sizeof(float)); } @@ -1711,9 +1711,9 @@ bool Bond::relevant() { || _peer->identity().address().toInt() == 0x795cbf86fa; } -SharedPtr Bond::getSlave(const SharedPtr& path) +SharedPtr Bond::getLink(const SharedPtr& path) { - return RR->bc->getSlaveBySocket(_policyAlias, path->localSocket()); + return RR->bc->getLinkBySocket(_policyAlias, path->localSocket()); } void Bond::dumpInfo(const int64_t now) @@ -1726,12 +1726,12 @@ void Bond::dumpInfo(const int64_t now) return; } /* - fprintf(stderr, "---[ bp=%d, id=%llx, dd=%d, up=%d, pmi=%d, specifiedSlaves=%d, _specifiedPrimarySlave=%d, _specifiedFailInst=%d ]\n", - _policy, _peer->identity().address().toInt(), _downDelay, _upDelay, _monitorInterval, _userHasSpecifiedSlaves, _userHasSpecifiedPrimarySlave, _userHasSpecifiedFailoverInstructions); + fprintf(stderr, "---[ bp=%d, id=%llx, dd=%d, up=%d, pmi=%d, specifiedLinks=%d, _specifiedPrimaryLink=%d, _specifiedFailInst=%d ]\n", + _policy, _peer->identity().address().toInt(), _downDelay, _upDelay, _monitorInterval, _userHasSpecifiedLinks, _userHasSpecifiedPrimaryLink, _userHasSpecifiedFailoverInstructions); if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { fprintf(stderr, "Paths (bp=%d, stats=%d, primaryReselect=%d) :\n", - _policy, _shouldCollectPathStatistics, _abSlaveSelectMethod); + _policy, _shouldCollectPathStatistics, _abLinkSelectMethod); } if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR @@ -1748,13 +1748,13 @@ void Bond::dumpInfo(const int64_t now) for(int i=0; i slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); _paths[i]->address().toString(pathStr); fprintf(stderr, " %2d: lat=%8.3f, ac=%3d, fail%5s, fscore=%6d, in=%7d, out=%7d, age=%7ld, ack=%7ld, ref=%6d, ls=%llx", i, _paths[i]->_latencyMean, _paths[i]->_allocation, - slave->failoverToSlave().c_str(), + link->failoverToLink().c_str(), _paths[i]->_failoverScore, _paths[i]->_packetsIn, _paths[i]->_packetsOut, @@ -1763,12 +1763,12 @@ void Bond::dumpInfo(const int64_t now) _paths[i]->_refractoryPeriod, _paths[i]->localSocket() ); - if (slave->spare()) { + if (link->spare()) { fprintf(stderr, " SPR."); } else { fprintf(stderr, " "); } - if (slave->primary()) { + if (link->primary()) { fprintf(stderr, " PRIM."); } else { fprintf(stderr, " "); @@ -1808,7 +1808,7 @@ void Bond::dumpInfo(const int64_t now) } else if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { fprintf(stderr, " "); } - fprintf(stderr, "%5s %s\n", slave->ifname().c_str(), pathStr); + fprintf(stderr, "%5s %s\n", link->ifname().c_str(), pathStr); } } @@ -1817,12 +1817,12 @@ void Bond::dumpInfo(const int64_t now) fprintf(stderr, "\nFailover Queue:\n"); for (std::list >::iterator it(_abFailoverQueue.begin()); it!=_abFailoverQueue.end();++it) { (*it)->address().toString(currPathStr); - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, (*it)->localSocket()); + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, (*it)->localSocket()); fprintf(stderr, "\t%8s\tspeed=%7d\trelSpeed=%3d\tipvPref=%3d\tfscore=%9d\t\t%s\n", - slave->ifname().c_str(), - slave->speed(), - slave->relativeSpeed(), - slave->ipvPref(), + link->ifname().c_str(), + link->speed(), + link->relativeSpeed(), + link->ipvPref(), (*it)->_failoverScore, currPathStr); } @@ -1840,15 +1840,15 @@ void Bond::dumpInfo(const int64_t now) fprintf(stderr, "\nBonded Paths:\n"); for (int i=0; i<_numBondedPaths; ++i) { _paths[_bondedIdx[i]]->address().toString(currPathStr); - SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[_bondedIdx[i]]->localSocket()); + SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[_bondedIdx[i]]->localSocket()); fprintf(stderr, " [%d]\t%8s\tflows=%3d\tspeed=%7d\trelSpeed=%3d\tipvPref=%3d\tfscore=%9d\t\t%s\n", i, //fprintf(stderr, " [%d]\t%8s\tspeed=%7d\trelSpeed=%3d\tflowCount=%2d\tipvPref=%3d\tfscore=%9d\t\t%s\n", i, - slave->ifname().c_str(), + link->ifname().c_str(), _paths[_bondedIdx[i]]->_assignedFlowCount, - slave->speed(), - slave->relativeSpeed(), + link->speed(), + link->relativeSpeed(), //_paths[_bondedIdx[i]].p->assignedFlows.size(), - slave->ipvPref(), + link->ipvPref(), _paths[_bondedIdx[i]]->_failoverScore, currPathStr); } diff --git a/node/Bond.hpp b/node/Bond.hpp index 353ed9317..4eee20f36 100644 --- a/node/Bond.hpp +++ b/node/Bond.hpp @@ -18,13 +18,13 @@ #include "Path.hpp" #include "Peer.hpp" -#include "../osdep/Slave.hpp" +#include "../osdep/Link.hpp" #include "Flow.hpp" namespace ZeroTier { class RuntimeEnvironment; -class Slave; +class Link; class Bond { @@ -52,7 +52,7 @@ public: void dumpInfo(const int64_t now); bool relevant(); - SharedPtr getSlave(const SharedPtr& path); + SharedPtr getLink(const SharedPtr& path); /** * Constructor. Creates a bond based off of ZT defaults @@ -281,7 +281,7 @@ public: void processActiveBackupTasks(int64_t now); /** - * Switches the active slave in an active-backup scenario to the next best during + * Switches the active link in an active-backup scenario to the next best during * a failover event. * * @param now Current time @@ -348,24 +348,24 @@ public: } /** - * @return Whether the user has defined slaves for use on this bond + * @return Whether the user has defined links for use on this bond */ - inline bool userHasSpecifiedSlaves() { return _userHasSpecifiedSlaves; } + inline bool userHasSpecifiedLinks() { return _userHasSpecifiedLinks; } /** - * @return Whether the user has defined a set of failover slave(s) for this bond + * @return Whether the user has defined a set of failover link(s) for this bond */ inline bool userHasSpecifiedFailoverInstructions() { return _userHasSpecifiedFailoverInstructions; }; /** - * @return Whether the user has specified a primary slave + * @return Whether the user has specified a primary link */ - inline bool userHasSpecifiedPrimarySlave() { return _userHasSpecifiedPrimarySlave; } + inline bool userHasSpecifiedPrimaryLink() { return _userHasSpecifiedPrimaryLink; } /** - * @return Whether the user has specified slave speeds + * @return Whether the user has specified link speeds */ - inline bool userHasSpecifiedSlaveSpeeds() { return _userHasSpecifiedSlaveSpeeds; } + inline bool userHasSpecifiedLinkSpeeds() { return _userHasSpecifiedLinkSpeeds; } /** * Periodically perform maintenance tasks for each active bond. @@ -441,7 +441,7 @@ public: /** * @param strategy Strategy that the bond uses to prob for path aliveness and quality */ - inline void setSlaveMonitorStrategy(uint8_t strategy) { _slaveMonitorStrategy = strategy; } + inline void setLinkMonitorStrategy(uint8_t strategy) { _linkMonitorStrategy = strategy; } /** * @return the current up delay parameter @@ -464,12 +464,12 @@ public: inline void setDownDelay(int downDelay) { if (downDelay >= 0) { _downDelay = downDelay; } } /** - * @return the current monitoring interval for the bond (can be overridden with intervals specific to certain slaves.) + * @return the current monitoring interval for the bond (can be overridden with intervals specific to certain links.) */ inline uint16_t getBondMonitorInterval() { return _bondMonitorInterval; } /** - * Set the current monitoring interval for the bond (can be overridden with intervals specific to certain slaves.) + * Set the current monitoring interval for the bond (can be overridden with intervals specific to certain links.) * * @param monitorInterval How often gratuitous VERB_HELLO(s) are sent to remote peer. */ @@ -498,21 +498,21 @@ public: /** * - * @param packetsPerSlave + * @param packetsPerLink */ - inline void setPacketsPerSlave(int packetsPerSlave) { _packetsPerSlave = packetsPerSlave; } + inline void setPacketsPerLink(int packetsPerLink) { _packetsPerLink = packetsPerLink; } /** * - * @param slaveSelectMethod + * @param linkSelectMethod */ - inline void setSlaveSelectMethod(uint8_t method) { _abSlaveSelectMethod = method; } + inline void setLinkSelectMethod(uint8_t method) { _abLinkSelectMethod = method; } /** * * @return */ - inline uint8_t getSlaveSelectMethod() { return _abSlaveSelectMethod; } + inline uint8_t getLinkSelectMethod() { return _abLinkSelectMethod; } /** * @@ -568,25 +568,25 @@ private: // active-backup SharedPtr _abPath; // current active path std::list > _abFailoverQueue; - uint8_t _abSlaveSelectMethod; // slave re-selection policy for the primary slave in active-backup + uint8_t _abLinkSelectMethod; // link re-selection policy for the primary link in active-backup uint64_t _lastActiveBackupPathChange; // balance-rr uint8_t _rrIdx; // index to path currently in use during Round Robin operation - uint16_t _rrPacketsSentOnCurrSlave; // number of packets sent on this slave since the most recent path switch. + uint16_t _rrPacketsSentOnCurrLink; // number of packets sent on this link since the most recent path switch. /** * How many packets will be sent on a path before moving to the next path * in the round-robin sequence. A value of zero will cause a random path * selection for each outgoing packet. */ - int _packetsPerSlave; + int _packetsPerLink; // balance-aware uint64_t _totalBondUnderload; uint8_t _flowRebalanceStrategy; - // dynamic slave monitoring - uint8_t _slaveMonitorStrategy; + // dynamic link monitoring + uint8_t _linkMonitorStrategy; uint64_t _lastFrame; uint32_t _dynamicPathMonitorInterval; @@ -651,14 +651,14 @@ private: Mutex _flows_m; /** - * Whether the user has specified slaves for this bond. + * Whether the user has specified links for this bond. */ - bool _userHasSpecifiedSlaves; + bool _userHasSpecifiedLinks; /** - * Whether the user has specified a primary slave for this bond. + * Whether the user has specified a primary link for this bond. */ - bool _userHasSpecifiedPrimarySlave; + bool _userHasSpecifiedPrimaryLink; /** * Whether the user has specified failover instructions for this bond. @@ -666,9 +666,9 @@ private: bool _userHasSpecifiedFailoverInstructions; /** - * Whether the user has specified slaves speeds for this bond. + * Whether the user has specified links speeds for this bond. */ - bool _userHasSpecifiedSlaveSpeeds; + bool _userHasSpecifiedLinkSpeeds; /** * How frequently (in ms) a VERB_ECHO is sent to a peer to verify that a diff --git a/node/BondController.cpp b/node/BondController.cpp index 6b21d9998..f7159dbc3 100644 --- a/node/BondController.cpp +++ b/node/BondController.cpp @@ -27,33 +27,33 @@ BondController::BondController(const RuntimeEnvironment *renv) : _defaultBondingPolicy = ZT_BONDING_POLICY_NONE; } -bool BondController::slaveAllowed(std::string &policyAlias, SharedPtr slave) +bool BondController::linkAllowed(std::string &policyAlias, SharedPtr link) { bool foundInDefinitions = false; - if (_slaveDefinitions.count(policyAlias)) { - auto it = _slaveDefinitions[policyAlias].begin(); - while (it != _slaveDefinitions[policyAlias].end()) { - if (slave->ifname() == (*it)->ifname()) { + if (_linkDefinitions.count(policyAlias)) { + auto it = _linkDefinitions[policyAlias].begin(); + while (it != _linkDefinitions[policyAlias].end()) { + if (link->ifname() == (*it)->ifname()) { foundInDefinitions = true; break; } ++it; } } - return _slaveDefinitions[policyAlias].empty() || foundInDefinitions; + return _linkDefinitions[policyAlias].empty() || foundInDefinitions; } -void BondController::addCustomSlave(std::string& policyAlias, SharedPtr slave) +void BondController::addCustomLink(std::string& policyAlias, SharedPtr link) { - Mutex::Lock _l(_slaves_m); - _slaveDefinitions[policyAlias].push_back(slave); - auto search = _interfaceToSlaveMap[policyAlias].find(slave->ifname()); - if (search == _interfaceToSlaveMap[policyAlias].end()) { - slave->setAsUserSpecified(true); - _interfaceToSlaveMap[policyAlias].insert(std::pair>(slave->ifname(), slave)); + Mutex::Lock _l(_links_m); + _linkDefinitions[policyAlias].push_back(link); + auto search = _interfaceToLinkMap[policyAlias].find(link->ifname()); + if (search == _interfaceToLinkMap[policyAlias].end()) { + link->setAsUserSpecified(true); + _interfaceToLinkMap[policyAlias].insert(std::pair>(link->ifname(), link)); } else { - fprintf(stderr, "slave already exists=%s\n", slave->ifname().c_str()); - // Slave is already defined, overlay user settings + fprintf(stderr, "link already exists=%s\n", link->ifname().c_str()); + // Link is already defined, overlay user settings } } @@ -115,20 +115,20 @@ SharedPtr BondController::createTransportTriggeredBond(const RuntimeEnviro /** * Determine if user has specified anything that could affect the bonding policy's decisions */ - if (_interfaceToSlaveMap.count(bond->policyAlias())) { - std::map >::iterator it = _interfaceToSlaveMap[bond->policyAlias()].begin(); - while (it != _interfaceToSlaveMap[bond->policyAlias()].end()) { + if (_interfaceToLinkMap.count(bond->policyAlias())) { + std::map >::iterator it = _interfaceToLinkMap[bond->policyAlias()].begin(); + while (it != _interfaceToLinkMap[bond->policyAlias()].end()) { if (it->second->isUserSpecified()) { - bond->_userHasSpecifiedSlaves = true; + bond->_userHasSpecifiedLinks = true; } if (it->second->isUserSpecified() && it->second->primary()) { - bond->_userHasSpecifiedPrimarySlave = true; + bond->_userHasSpecifiedPrimaryLink = true; } if (it->second->isUserSpecified() && it->second->userHasSpecifiedFailoverInstructions()) { bond->_userHasSpecifiedFailoverInstructions = true; } if (it->second->isUserSpecified() && (it->second->speed() > 0)) { - bond->_userHasSpecifiedSlaveSpeeds = true; + bond->_userHasSpecifiedLinkSpeeds = true; } ++it; } @@ -138,16 +138,16 @@ SharedPtr BondController::createTransportTriggeredBond(const RuntimeEnviro return SharedPtr(); } -SharedPtr BondController::getSlaveBySocket(const std::string& policyAlias, uint64_t localSocket) +SharedPtr BondController::getLinkBySocket(const std::string& policyAlias, uint64_t localSocket) { - Mutex::Lock _l(_slaves_m); + Mutex::Lock _l(_links_m); char ifname[16]; _phy->getIfName((PhySocket *) ((uintptr_t)localSocket), ifname, 16); std::string ifnameStr(ifname); - auto search = _interfaceToSlaveMap[policyAlias].find(ifnameStr); - if (search == _interfaceToSlaveMap[policyAlias].end()) { - SharedPtr s = new Slave(ifnameStr, 0, 0, 0, 0, 0, true, ZT_MULTIPATH_SLAVE_MODE_SPARE, "", 0.0); - _interfaceToSlaveMap[policyAlias].insert(std::pair >(ifnameStr, s)); + auto search = _interfaceToLinkMap[policyAlias].find(ifnameStr); + if (search == _interfaceToLinkMap[policyAlias].end()) { + SharedPtr s = new Link(ifnameStr, 0, 0, 0, 0, 0, true, ZT_MULTIPATH_SLAVE_MODE_SPARE, "", 0.0); + _interfaceToLinkMap[policyAlias].insert(std::pair >(ifnameStr, s)); return s; } else { @@ -155,14 +155,14 @@ SharedPtr BondController::getSlaveBySocket(const std::string& policyAlias } } -SharedPtr BondController::getSlaveByName(const std::string& policyAlias, const std::string& ifname) +SharedPtr BondController::getLinkByName(const std::string& policyAlias, const std::string& ifname) { - Mutex::Lock _l(_slaves_m); - auto search = _interfaceToSlaveMap[policyAlias].find(ifname); - if (search != _interfaceToSlaveMap[policyAlias].end()) { + Mutex::Lock _l(_links_m); + auto search = _interfaceToLinkMap[policyAlias].find(ifname); + if (search != _interfaceToLinkMap[policyAlias].end()) { return search->second; } - return SharedPtr(); + return SharedPtr(); } bool BondController::allowedToBind(const std::string& ifname) @@ -172,18 +172,18 @@ bool BondController::allowedToBind(const std::string& ifname) if (!_defaultBondingPolicy) { return true; // no restrictions } - Mutex::Lock _l(_slaves_m); - if (_interfaceToSlaveMap.empty()) { + Mutex::Lock _l(_links_m); + if (_interfaceToLinkMap.empty()) { return true; // no restrictions } - std::map > >::iterator policyItr = _interfaceToSlaveMap.begin(); - while (policyItr != _interfaceToSlaveMap.end()) { - std::map >::iterator slaveItr = policyItr->second.begin(); - while (slaveItr != policyItr->second.end()) { - if (slaveItr->first == ifname) { + std::map > >::iterator policyItr = _interfaceToLinkMap.begin(); + while (policyItr != _interfaceToLinkMap.end()) { + std::map >::iterator linkItr = policyItr->second.begin(); + while (linkItr != policyItr->second.end()) { + if (linkItr->first == ifname) { return true; } - ++slaveItr; + ++linkItr; } ++policyItr; } diff --git a/node/BondController.hpp b/node/BondController.hpp index 95fbf81fc..2e0c15072 100644 --- a/node/BondController.hpp +++ b/node/BondController.hpp @@ -19,7 +19,7 @@ #include "SharedPtr.hpp" #include "../osdep/Phy.hpp" -#include "../osdep/Slave.hpp" +#include "../osdep/Link.hpp" namespace ZeroTier { @@ -36,9 +36,9 @@ public: BondController(const RuntimeEnvironment *renv); /** - * @return Whether this slave is permitted to become a member of a bond. + * @return Whether this link is permitted to become a member of a bond. */ - bool slaveAllowed(std::string &policyAlias, SharedPtr slave); + bool linkAllowed(std::string &policyAlias, SharedPtr link); /** * @return The minimum interval required to poll the active bonds to fulfill all active monitoring timing requirements. @@ -103,12 +103,12 @@ public: static int defaultBondingPolicy() { return _defaultBondingPolicy; } /** - * Add a user-defined slave to a given bonding policy. + * Add a user-defined link to a given bonding policy. * * @param policyAlias User-defined custom name for variant of bonding policy - * @param slave Pointer to new slave definition + * @param link Pointer to new link definition */ - void addCustomSlave(std::string& policyAlias, SharedPtr slave); + void addCustomLink(std::string& policyAlias, SharedPtr link); /** * Add a user-defined bonding policy that is based on one of the standard types. @@ -145,22 +145,22 @@ public: void processBackgroundTasks(void *tPtr, int64_t now); /** - * Gets a reference to a physical slave definition given a policy alias and a local socket. + * Gets a reference to a physical link definition given a policy alias and a local socket. * * @param policyAlias Policy in use * @param localSocket Local source socket - * @return Physical slave definition + * @return Physical link definition */ - SharedPtr getSlaveBySocket(const std::string& policyAlias, uint64_t localSocket); + SharedPtr getLinkBySocket(const std::string& policyAlias, uint64_t localSocket); /** - * Gets a reference to a physical slave definition given its human-readable system name. + * Gets a reference to a physical link definition given its human-readable system name. * * @param policyAlias Policy in use * @param ifname Alphanumeric human-readable name - * @return Physical slave definition + * @return Physical link definition */ - SharedPtr getSlaveByName(const std::string& policyAlias, const std::string& ifname); + SharedPtr getLinkByName(const std::string& policyAlias, const std::string& ifname); /** * @param ifname Name of interface that we want to know if we can bind to @@ -175,7 +175,7 @@ private: const RuntimeEnvironment *RR; Mutex _bonds_m; - Mutex _slaves_m; + Mutex _links_m; /** * The last time that the bond controller updated the set of bonds. @@ -213,14 +213,14 @@ private: std::map > _bondPolicyTemplates; /** - * Set of slaves defined for a given bonding policy + * Set of links defined for a given bonding policy */ - std::map > > _slaveDefinitions; + std::map > > _linkDefinitions; /** - * Set of slave objects mapped to their physical interfaces + * Set of link objects mapped to their physical interfaces */ - std::map > > _interfaceToSlaveMap; + std::map > > _interfaceToLinkMap; // TODO: Remove uint64_t bondStartTime; diff --git a/node/Constants.hpp b/node/Constants.hpp index 9f2cd80a5..9b1d21f9f 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -405,12 +405,12 @@ #define ZT_FLOW_MAX_COUNT (1024*64) /** - * How often flows are rebalanced across slave interfaces (if at all) + * How often flows are rebalanced across link (if at all) */ #define ZT_FLOW_MIN_REBALANCE_INTERVAL 5000 /** - * How often flows are rebalanced across slave interfaces (if at all) + * How often flows are rebalanced across link (if at all) */ #define ZT_FLOW_REBALANCE_INTERVAL 5000 @@ -428,7 +428,7 @@ /** * Minimum amount of time (since a previous transition) before the active-backup bonding - * policy is allowed to transition to a different slave. Only valid for active-backup. + * policy is allowed to transition to a different link. Only valid for active-backup. */ #define ZT_MULTIPATH_MIN_ACTIVE_BACKUP_AUTOFLOP_INTERVAL 10000 diff --git a/node/Node.cpp b/node/Node.cpp index e71c1424c..16484dac0 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -501,14 +501,12 @@ ZT_PeerList *Node::peers() const p->pathCount = 0; for(std::vector< SharedPtr >::iterator path(paths.begin());path!=paths.end();++path) { memcpy(&(p->paths[p->pathCount].address),&((*path)->address()),sizeof(struct sockaddr_storage)); - //memcpy(&(p->paths[p->pathCount].ifname,&((*path)->slave()),32);) p->paths[p->pathCount].localSocket = (*path)->localSocket(); p->paths[p->pathCount].lastSend = (*path)->lastOut(); p->paths[p->pathCount].lastReceive = (*path)->lastIn(); p->paths[p->pathCount].trustedPathId = RR->topology->getOutboundPathTrust((*path)->address()); p->paths[p->pathCount].expired = 0; p->paths[p->pathCount].preferred = ((*path) == bestp) ? 1 : 0; - //p->paths[p->pathCount].age = (*path)->age(_now); p->paths[p->pathCount].scope = (*path)->ipScope(); ++p->pathCount; } diff --git a/node/Path.hpp b/node/Path.hpp index 1cbd588bc..5a3a1ef82 100644 --- a/node/Path.hpp +++ b/node/Path.hpp @@ -29,7 +29,7 @@ #include "Packet.hpp" #include "RingBuffer.hpp" -#include "../osdep/Slave.hpp" +#include "../osdep/Link.hpp" /** * Maximum return value of preferenceRank() @@ -103,7 +103,7 @@ public: _downDelay(0), _ipvPref(0), _mode(0), - _onlyPathOnSlave(false), + _onlyPathOnLink(false), _enabled(false), _bonded(false), _negotiated(false), @@ -152,7 +152,7 @@ public: _downDelay(0), _ipvPref(0), _mode(0), - _onlyPathOnSlave(false), + _onlyPathOnLink(false), _enabled(false), _bonded(false), _negotiated(false), @@ -431,10 +431,10 @@ public: } /** - * @return True if a path is preferred over another on the same physical slave (according to user pref.) + * @return True if a path is preferred over another on the same physical link (according to user pref.) */ inline bool preferred() { - return _onlyPathOnSlave + return _onlyPathOnLink || (_addr.isV4() && (_ipvPref == 4 || _ipvPref == 46)) || (_addr.isV6() && (_ipvPref == 6 || _ipvPref == 64)); } @@ -549,22 +549,22 @@ private: uint32_t _downDelay; /** - * IP version preference inherited from the physical slave. + * IP version preference inherited from the physical link. */ uint8_t _ipvPref; /** - * Mode inherited from the physical slave. + * Mode inherited from the physical link. */ uint8_t _mode; /** - * IP version preference inherited from the physical slave. + * IP version preference inherited from the physical link. */ - bool _onlyPathOnSlave; + bool _onlyPathOnLink; /** - * Enabled state inherited from the physical slave. + * Enabled state inherited from the physical link. */ bool _enabled; diff --git a/node/Peer.cpp b/node/Peer.cpp index 30911b43c..565118867 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -55,8 +55,8 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident _remoteMultipathSupported(false), _canUseMultipath(false), _shouldCollectPathStatistics(0), - _lastComputedAggregateMeanLatency(0), - _bondingPolicy(0) + _bondingPolicy(0), + _lastComputedAggregateMeanLatency(0) { if (!myIdentity.agree(peerIdentity,_key,ZT_PEER_SECRET_KEY_LENGTH)) { throw ZT_EXCEPTION_INVALID_ARGUMENT; diff --git a/osdep/Binder.hpp b/osdep/Binder.hpp index 0fde33452..8f572a4f5 100644 --- a/osdep/Binder.hpp +++ b/osdep/Binder.hpp @@ -347,11 +347,11 @@ public: } } - // Generate set of unique interface names (used for formation of logical slave set in multipath code) + // Generate set of unique interface names (used for formation of logical link set in multipath code) for(std::map::const_iterator ii(localIfAddrs.begin());ii!=localIfAddrs.end();++ii) { - slaveIfNames.insert(ii->second); + linkIfNames.insert(ii->second); } - for (std::set::iterator si(slaveIfNames.begin());si!=slaveIfNames.end();si++) { + for (std::set::iterator si(linkIfNames.begin());si!=linkIfNames.end();si++) { bool bFoundMatch = false; for(std::map::const_iterator ii(localIfAddrs.begin());ii!=localIfAddrs.end();++ii) { if (ii->second == *si) { @@ -360,7 +360,7 @@ public: } } if (!bFoundMatch) { - slaveIfNames.erase(si); + linkIfNames.erase(si); } } @@ -461,15 +461,15 @@ public: return false; } - inline std::set getSlaveInterfaceNames() + inline std::set getLinkInterfaceNames() { Mutex::Lock _l(_lock); - return slaveIfNames; + return linkIfNames; } private: - std::set slaveIfNames; + std::set linkIfNames; _Binding _bindings[ZT_BINDER_MAX_BINDINGS]; std::atomic _bindingCount; Mutex _lock; diff --git a/osdep/Slave.hpp b/osdep/Link.hpp similarity index 58% rename from osdep/Slave.hpp rename to osdep/Link.hpp index a4caa983f..6cbbbdfbe 100644 --- a/osdep/Slave.hpp +++ b/osdep/Link.hpp @@ -11,8 +11,8 @@ */ /****/ -#ifndef ZT_SLAVE_HPP -#define ZT_SLAVE_HPP +#ifndef ZT_LINK_HPP +#define ZT_LINK_HPP #include @@ -20,13 +20,13 @@ namespace ZeroTier { -class Slave +class Link { - friend class SharedPtr; + friend class SharedPtr; public: - Slave() {} + Link() {} /** * @@ -35,60 +35,60 @@ public: * @param speed * @param enabled * @param mode - * @param failoverToSlaveStr + * @param failoverToLinkStr * @param userSpecifiedAlloc */ - Slave(std::string& ifnameStr, + Link(std::string& ifnameStr, uint8_t ipvPref, uint32_t speed, - uint32_t slaveMonitorInterval, + uint32_t linkMonitorInterval, uint32_t upDelay, uint32_t downDelay, bool enabled, uint8_t mode, - std::string failoverToSlaveStr, + std::string failoverToLinkStr, float userSpecifiedAlloc) : _ifnameStr(ifnameStr), _ipvPref(ipvPref), _speed(speed), _relativeSpeed(0), - _slaveMonitorInterval(slaveMonitorInterval), + _linkMonitorInterval(linkMonitorInterval), _upDelay(upDelay), _downDelay(downDelay), _enabled(enabled), _mode(mode), - _failoverToSlaveStr(failoverToSlaveStr), + _failoverToLinkStr(failoverToLinkStr), _userSpecifiedAlloc(userSpecifiedAlloc), _isUserSpecified(false) {} /** - * @return The string representation of this slave's underlying interface's system name. + * @return The string representation of this link's underlying interface's system name. */ inline std::string ifname() { return _ifnameStr; } /** - * @return Whether this slave is designated as a primary. + * @return Whether this link is designated as a primary. */ inline bool primary() { return _mode == ZT_MULTIPATH_SLAVE_MODE_PRIMARY; } /** - * @return Whether this slave is designated as a spare. + * @return Whether this link is designated as a spare. */ inline bool spare() { return _mode == ZT_MULTIPATH_SLAVE_MODE_SPARE; } /** - * @return The name of the slave interface that should be used in the event of a failure. + * @return The name of the link interface that should be used in the event of a failure. */ - inline std::string failoverToSlave() { return _failoverToSlaveStr; } + inline std::string failoverToLink() { return _failoverToLinkStr; } /** - * @return Whether this slave interface was specified by the user or auto-detected. + * @return Whether this link interface was specified by the user or auto-detected. */ inline bool isUserSpecified() { return _isUserSpecified; } /** - * Signify that this slave was specified by the user and not the result of auto-detection. + * Signify that this link was specified by the user and not the result of auto-detection. * * @param isUserSpecified */ @@ -97,59 +97,59 @@ public: /** * @return Whether or not the user has specified failover instructions. */ - inline bool userHasSpecifiedFailoverInstructions() { return _failoverToSlaveStr.length(); } + inline bool userHasSpecifiedFailoverInstructions() { return _failoverToLinkStr.length(); } /** - * @return The speed of the slave relative to others in the bond. + * @return The speed of the link relative to others in the bond. */ inline uint8_t relativeSpeed() { return _relativeSpeed; } /** - * Sets the speed of the slave relative to others in the bond. + * Sets the speed of the link relative to others in the bond. * - * @param relativeSpeed The speed relative to the rest of the slave interfaces. + * @param relativeSpeed The speed relative to the rest of the link. */ inline void setRelativeSpeed(uint8_t relativeSpeed) { _relativeSpeed = relativeSpeed; } /** - * Sets the speed of the slave relative to others in the bond. + * Sets the speed of the link relative to others in the bond. * * @param relativeSpeed */ - inline void setMonitorInterval(uint32_t interval) { _slaveMonitorInterval = interval; } + inline void setMonitorInterval(uint32_t interval) { _linkMonitorInterval = interval; } /** - * @return The absolute speed of the slave interface (as specified by the user.) + * @return The absolute speed of the link (as specified by the user.) */ - inline uint32_t monitorInterval() { return _slaveMonitorInterval; } + inline uint32_t monitorInterval() { return _linkMonitorInterval; } /** - * @return The absolute speed of the slave interface (as specified by the user.) + * @return The absolute speed of the link (as specified by the user.) */ inline uint32_t speed() { return _speed; } /** - * @return The address preference for this slave interface (as specified by the user.) + * @return The address preference for this link (as specified by the user.) */ inline uint8_t ipvPref() { return _ipvPref; } /** - * @return The mode (e.g. primary/spare) for this slave interface (as specified by the user.) + * @return The mode (e.g. primary/spare) for this link (as specified by the user.) */ inline uint8_t mode() { return _mode; } /** - * @return The upDelay parameter for all paths on this slave interface. + * @return The upDelay parameter for all paths on this link. */ inline uint32_t upDelay() { return _upDelay; } /** - * @return The downDelay parameter for all paths on this slave interface. + * @return The downDelay parameter for all paths on this link. */ inline uint32_t downDelay() { return _downDelay; } /** - * @return Whether this slave is enabled or disabled + * @return Whether this link is enabled or disabled */ inline uint8_t enabled() { return _enabled; } @@ -173,21 +173,21 @@ private: uint8_t _ipvPref; /** - * User-specified speed of this slave/link + * User-specified speed of this link */ uint32_t _speed; /** - * Speed relative to other specified slaves/links (computed by Bond) + * Speed relative to other specified links (computed by Bond) */ uint8_t _relativeSpeed; /** - * User-specified interval for monitoring paths on this specific slave + * User-specified interval for monitoring paths on this specific link * instead of using the more generic interval specified for the entire * bond. */ - uint32_t _slaveMonitorInterval; + uint32_t _linkMonitorInterval; /** * How long before a path is considered to be usable after coming online. (when using policies that @@ -202,20 +202,20 @@ private: uint32_t _downDelay; /** - * Whether this slave is enabled, or (disabled (possibly bad config)) + * Whether this link is enabled, or (disabled (possibly bad config)) */ uint8_t _enabled; /** - * Whether this slave is designated as a primary, a spare, or no preference. + * Whether this link is designated as a primary, a spare, or no preference. */ uint8_t _mode; /** - * The specific name of the interface to be used in the event that this - * slave fails. + * The specific name of the link to be used in the event that this + * link fails. */ - std::string _failoverToSlaveStr; + std::string _failoverToLinkStr; /** * User-specified allocation @@ -223,7 +223,7 @@ private: float _userSpecifiedAlloc; /** - * Whether or not this slave was created as a result of manual user specification. This is + * Whether or not this link was created as a result of manual user specification. This is * important to know because certain policy decisions are dependent on whether the user * intents to use a specific set of interfaces. */ diff --git a/service/MULTIPATH.md b/service/MULTIPATH.md index 8a9e84603..106125a72 100644 --- a/service/MULTIPATH.md +++ b/service/MULTIPATH.md @@ -1,17 +1,17 @@ -### **2.1.5.** Link aggregation +### Bonding (link aggregation) -Link aggregation allows the simultaneous (or conditional) use of multiple physical links to enable increased throughput, load balancing, redundancy, and fault tolerance. There are a variety of standard policies available that can be used right out of the box with little to no configuration. These policies are directly inspired by [the policies offered by the Linux kernel](https://www.kernel.org/doc/Documentation/networking/bonding.txt). +Link aggregation allows the simultaneous (or conditional) use of multiple physical links to enable increased throughput, load balancing, redundancy, and fault tolerance. There are a variety of standard policies available that can be used right out of the box with little to no configuration. These policies are directly inspired by [the policies offered by the Linux kernel](https://www.kernel.org/doc/Documentation/networking/bonding.txt) but are now offered in user-space and hence available on all platforms that ZeroTier supports. -#### Standard Policies +#### Standard policies -| Policy name | Fault tolerance | Min. failover (sec.) | Default Failover (sec.) | Balancing | Aggregation efficiency | Redundancy | Sequence Reordering | -|--------------------|:---------------------:|---------------------:|---------------------:|----------------------:|-----------------------:|-----------:|--------------------:| -| `none` | None | `60+` | `60+` | none | `none` |1 | No -| `active-backup` | Brief interruption | `0.25` | `10` | none | `low` |1 | Only during failover -| `broadcast` | Fully tolerant | `N/A` | `N/A` | none | `very low` |N | Often -| `balance-rr` | Self-healing | `0.25` | `10` | packet-based | `high` |1 | Often -| `balance-xor` | Self-healing | `0.25` | `10` | flow-based | `very high` |1 | Only during failover -| `balance-aware` | Self-healing | `0.25` | `10` | *adaptive* flow-based | `very high` |1 | Only during failover and re-balance +| Policy name | Fault tolerance | Min. failover (sec.) | Default Failover (sec.)| Balancing | Aggregation efficiency | Redundancy | Sequence Reordering | +|--------------------|:---------------------:|---------------------:|-----------------------:|----------------------:|-----------------------:|-----------:|--------------------:| +| `none` | None | `60+` | `60+` | none | `none` |1 | No +| `active-backup` | Brief interruption | `0.25` | `10` | none | `low` |1 | Only during failover +| `broadcast` | Fully tolerant | `N/A` | `N/A` | none | `very low` |N | Often +| `balance-rr` | Self-healing | `0.25` | `10` | packet-based | `high` |1 | Often +| `balance-xor` | Self-healing | `0.25` | `10` | flow-based | `very high` |1 | Only during failover +| `balance-aware` | Self-healing | `0.25` | `10` | *adaptive* flow-based | `very high` |1 | Only during failover and re-balance A policy can be used easily without specifying any additional parameters: @@ -23,7 +23,7 @@ A policy can be used easily without specifying any additional parameters: } ``` -#### Custom Policies +#### Custom policies To customize a bonding policy for your use-case simply specify a `basePolicy` and override chosen parameters. For example, to create a more aggressive `active-backup` policy with low monitoring overhead that will failover `0.250` seconds after it detects a link failure, one could do the following: @@ -45,11 +45,11 @@ To customize a bonding policy for your use-case simply specify a `basePolicy` an } ``` -#### Specifying Slave interfaces +#### Specifying links -Available system network interfaces are referred to as `slaves`. Different sets of slaves can be constructed for different bonding policies and used simultaneously. One can specify the links that ZeroTier should use in any given bonding policy simply by providing an array of slaves with names corresponding to interface names. If a user doesn't specify a set of interfaces to use, ZeroTier will assume every system interface is available for use. However, if the user **does** specify a set of interfaces, ZeroTier will only use what is specified. The same applies to failover rules, if none are specified, ZeroTier will failover to any operational slave. On the other hand, if the user does specify failover rules and there is ever a situation where a slave is available for usage but does not fit within the rules specified by the user, it will go unused. +Bonds are composed of multiple `links`. Different sets of links can be constructed for different bonding policies and used simultaneously. One can specify the links that ZeroTier should use in any given bonding policy simply by providing an array of links with names corresponding to interface names. If a user doesn't specify a set of interfaces to use, ZeroTier will assume every system interface is available for use. However, if the user **does** specify a set of interfaces, ZeroTier will only use what is specified. The same applies to failover rules, if none are specified, ZeroTier will failover to any operational link. On the other hand, if the user does specify failover rules and there is ever a situation where a link is available for usage but does not fit within the rules specified by the user, it will go unused. -To specify that ZeroTier should only use `eth0` and `eth1` as primary slaves, and `eth2` as a backup spare and that it should prefer IPv4 over IPv6 except on `eth2` where only IPv6 is allowed: +To specify that ZeroTier should only use `eth0` and `eth1` as primary links, and `eth2` as a backup spare and that it should prefer IPv4 over IPv6 except on `eth2` where only IPv6 is allowed: ``` { @@ -57,7 +57,7 @@ To specify that ZeroTier should only use `eth0` and `eth1` as primary slaves, an "defaultBondingPolicy": "aggressive-active-backup", "policies": { "aggressive-active-backup": { - "slaves": { + "links": { "eth0": { "ipvPref": 46, "failoverTo": "eth2", @@ -79,27 +79,27 @@ To specify that ZeroTier should only use `eth0` and `eth1` as primary slaves, an } ``` -Additional slave-specific parameters: +Additional link-specific parameters: ``` -"slaves": +"links": { "interfaceName": /* System-name of the network interface. */ { - "failoverInterval": 0-65535, /* (optional) How quickly a path on this slave should failover after a detected failure. */ - "ipvPref": [0,4,6,46,64], /* (optional) IP version preference for detected paths on a slave. */ - "speed": 0-1000000, /* (optional) How fast this slave is (in arbitrary units). This is a useful way to manually allocate a bond. */ + "failoverInterval": 0-65535, /* (optional) How quickly a path on this link should failover after a detected failure. */ + "ipvPref": [0,4,6,46,64], /* (optional) IP version preference for detected paths on a link. */ + "speed": 0-1000000, /* (optional) How fast this link is (in arbitrary units). This is a useful way to manually allocate a bond. */ "alloc": 0-255, /* (optional) A relative value representing a desired allocation. */ "upDelay": 0-65535, /* (optional) How long after a path becomes alive before it is added to the bond. */ "downDelay": 0-65535, /* (optional) How long after a path fails before it is removed from the bond. */ - "failoverTo": "spareInterfaceName", /* (optional) Which slave should be used next after a failure of this slave. */ - "enabled": true|false, /* (optional) Whether any paths on this slave are allowed to be used this bond. */ - "mode": "primary"|"spare" /* (optional) Whether this slave is used by default or only after failover events. */ + "failoverTo": "spareInterfaceName", /* (optional) Which link should be used next after a failure of this link. */ + "enabled": true|false, /* (optional) Whether any paths on this link are allowed to be used this bond. */ + "mode": "primary"|"spare" /* (optional) Whether this link is used by default or only after failover events. */ } } ``` -#### Peer-specific Bonds +#### Peer-specific bonds It is possible to direct ZeroTier to form a certain type of bond with specific peers of your choice. For instance, if one were to want `active-backup` by default but for certain peers to be bonded with a custom load-balanced bond such as `my-custom-balance-aware` one could do the following: @@ -127,17 +127,17 @@ It is possible to direct ZeroTier to form a certain type of bond with specific p } ``` -#### Active Backup (`active-backup`) +#### Active backup (`active-backup`) Traffic is sent only on (one) path at any given time. A different path becomes active if the current path fails. This mode provides fault tolerance with a nearly immediate fail-over. This mode **does not** increase total throughput. - - `mode`: `primary, spare` Slave option which specifies which slave is the primary device. The specified device is intended to always be the active slave while it is available. There are exceptions to this behavior when using different `slaveSelectMethod` modes. There can only be one `primary` slave in this bonding policy. + - `mode`: `primary, spare` Link option which specifies which link is the primary device. The specified device is intended to always be the active link while it is available. There are exceptions to this behavior when using different `linkSelectMethod` modes. There can only be one `primary` link in this bonding policy. - - `slaveSelectMethod`: Specifies the selection policy for the active slave during failure and/or recovery events. This is similar to the Linux Kernel's `primary_reselect` option but with a minor extension: - - `optimize`: **(default if user provides no failover guidance)** The primary slave can change periodically if a superior path is detected. - - `always`: **(default when slaves are explicitly specified)**: Primary slave regains status as active slave whenever it comes back up. - - `better`: Primary slave regains status as active slave when it comes back up and (if) it is better than the currently-active slave. - - `failure`: Primary slave regains status as active slave only if the currently-active slave fails. + - `linkSelectMethod`: Specifies the selection policy for the active link during failure and/or recovery events. This is similar to the Linux Kernel's `primary_reselect` option but with a minor extension: + - `optimize`: **(default if user provides no failover guidance)** The primary link can change periodically if a superior path is detected. + - `always`: **(default when links are explicitly specified)**: Primary link regains status as active link whenever it comes back up. + - `better`: Primary link regains status as active link when it comes back up and (if) it is better than the currently-active link. + - `failure`: Primary link regains status as active link only if the currently-active link fails. ``` { @@ -146,8 +146,8 @@ Traffic is sent only on (one) path at any given time. A different path becomes a "defaultBondingPolicy": "active-backup", "active-backup": { - "slaveSelectMethod": "always", - "slaves": + "linkSelectMethod": "always", + "links": { "eth0": { "failoverTo": "eth1", "mode": "primary" }, "eth1": { "mode": "spare" }, @@ -163,17 +163,17 @@ Traffic is sent only on (one) path at any given time. A different path becomes a Traffic is sent on (all) available paths simultaneously. This mode provides fault tolerance and effectively immediate failover due to transmission redundancy. This mode is a poor utilization of throughput resources and will **not** increase throughput but can prevent packet loss during a link failure. The only option available is `dedup` which will de-duplicate all packets on the receiving end if set to `true`. -#### Balance Round Robin (`balance-rr`) +#### Balance round robin (`balance-rr`) -Traffic is striped across multiple paths. Offers partial fault tolerance immediately, full fault tolerance eventually. This policy is unaware of protocols and is primarily intended for use with protocols that are not sensitive to reordering delays. The only option available for this policy is `packetsPerSlave` which specifies the number of packets to transmit via a path before moving to the next in the RR sequence. When set to `0` a path is chosen at random for each outgoing packet. The default value is `8`, low values can begin to add overhead to packet processing. +Traffic is striped across multiple paths. Offers partial fault tolerance immediately, full fault tolerance eventually. This policy is unaware of protocols and is primarily intended for use with protocols that are not sensitive to reordering delays. The only option available for this policy is `packetsPerLink` which specifies the number of packets to transmit via a path before moving to the next in the RR sequence. When set to `0` a path is chosen at random for each outgoing packet. The default value is `8`, low values can begin to add overhead to packet processing. #### Balance XOR (`balance-xor`, similar to the Linux kernel's [balance-xor](https://www.kernel.org/doc/Documentation/networking/bonding.txt) with `xmit_hash_policy=layer3+4`) -Traffic is categorized into *flows* based on *source port*, *destination port*, and *protocol type* these flows are then hashed onto available slaves. Each flow will persist on its assigned slave interface for its entire life-cycle. Traffic that does not have an assigned port (such as ICMP pings) will be randomly distributed across slaves. The hash function is simply: `src_port ^ dst_port ^ proto`. +Traffic is categorized into *flows* based on *source port*, *destination port*, and *protocol type* these flows are then hashed onto available links. Each flow will persist on its assigned link interface for its entire life-cycle. Traffic that does not have an assigned port (such as ICMP pings) will be randomly distributed across links. The hash function is simply: `src_port ^ dst_port ^ proto`. -#### Balance Aware (`balance-aware`, similar to Linux kernel's [`balance-*lb`](https://www.kernel.org/doc/Documentation/networking/bonding.txt) modes) +#### Balance aware (`balance-aware`, similar to Linux kernel's [`balance-*lb`](https://www.kernel.org/doc/Documentation/networking/bonding.txt) modes) -Traffic is dynamically allocated and balanced across multiple slaves simultaneously according to the target allocation. Options allow for *packet* or *flow-based* processing, and active-flow reassignment. Flows mediated over a recently failed slaves will be reassigned in a manner that respects the target allocation of the bond. An optional `balancePolicy` can be specified with the following effects: `flow-dynamic` (default) will hash flows onto slaves according to target allocation and may perform periodic re-assignments in order to preserve balance. `flow-static`, will hash flows onto slaves according to target allocation but will not re-assign flows unless a failure occurs or the slave is no longer operating within acceptable parameters. And lastly `packet` which simply load balances packets across slaves according to target allocation but with no concern for sequence reordering. +Traffic is dynamically allocated and balanced across multiple links simultaneously according to the target allocation. Options allow for *packet* or *flow-based* processing, and active-flow reassignment. Flows mediated over a recently failed links will be reassigned in a manner that respects the target allocation of the bond. An optional `balancePolicy` can be specified with the following effects: `flow-dynamic` (default) will hash flows onto links according to target allocation and may perform periodic re-assignments in order to preserve balance. `flow-static`, will hash flows onto links according to target allocation but will not re-assign flows unless a failure occurs or the link is no longer operating within acceptable parameters. And lastly `packet` which simply load balances packets across links according to target allocation but with no concern for sequence reordering. ``` { @@ -187,21 +187,21 @@ Traffic is dynamically allocated and balanced across multiple slaves simultaneou } ``` -#### Link Quality +#### Link quality ZeroTier measures various properties of a link (such as latency, throughput, jitter, packet loss ratio, etc) in order to arrive at a quality estimate. This estimate is used by bonding policies to make allocation and failover decisions: | Policy name | Role | |:---------------|:-----| -|`active-backup` | Determines the order of the failover queue. And if `activeReselect=optimize` whether a new active slave is selected. | +|`active-backup` | Determines the order of the failover queue. And if `activeReselect=optimize` whether a new active link is selected. | |`broadcast` | Does not use quality measurements. | -|`balance-rr` | May trigger removal of slave from bond. | -|`balance-xor` | May trigger removal of slave from bond. | -|`balance-aware` | Informs flow assignments and (re-)assignments. May trigger removal of slave from bond. | +|`balance-rr` | May trigger removal of link from bond. | +|`balance-xor` | May trigger removal of link from bond. | +|`balance-aware` | Informs flow assignments and (re-)assignments. May trigger removal of link from bond. | -A slave's eligibility for being included in a bond is dependent on more than perceived quality. If a path on a slave begins to exhibit disruptive behavior such as extremely high packet loss, corruption, or periodic inability to process traffic it will be removed from the bond, its traffic will be appropriately reallocated and it will be punished. Punishments gradually fade and a slave can be readmitted to the bond over time. However, punishments increase exponentially if applied more than once within a given window of time. +A link's eligibility for being included in a bond is dependent on more than perceived quality. If a path on a link begins to exhibit disruptive behavior such as extremely high packet loss, corruption, or periodic inability to process traffic it will be removed from the bond, its traffic will be appropriately reallocated and it will be punished. Punishments gradually fade and a link can be readmitted to the bond over time. However, punishments increase exponentially if applied more than once within a given window of time. -#### Asymmetric Links +#### Asymmetric links In cases where it is necessary to bond physical links that vary radically in terms of cost, throughput, latency, and or reliability, there are a couple of ways to automatically (or manually) allocate traffic among them. Traffic distribution and balancing can be either `packet` or `flow` based. Where packet-based is suitable for protocols not susceptible to reordering penalties and flow-based is suitable for protocols such as TCP where it is desirable to keep a conversation on a single link unless we can't avoid having to re-assign it. Additionally, a *target allocation* of traffic used by the bonding policy can be derived/specified in the following ways: @@ -215,9 +215,6 @@ In cases where it is necessary to bond physical links that vary radically in ter "pdv": 0.3, /* Packet delay variance in milliseconds. Similar to jitter */ "plr": 0.1, /* Packet loss ratio */ "per": 0.1, /* Packet error ratio */ - "thr": 0.0, /* Mean throughput */ - "thm": 0.0, /* Maximum observed throughput */ - "thv": 0.0, /* Variance of throughput */ "avl": 0.0, /* Availability */ } } @@ -225,24 +222,24 @@ In cases where it is necessary to bond physical links that vary radically in ter In the absence of user guidance ZeroTier will attempt to form an understanding of each link's speed and capacity but this value can be inaccurate if the links are not routinely saturated. Therefore we provide a way to explicitly signal the capacity of each link in terms of arbitrary but relative values: ``` -"slaves": { +"links": { "eth0": { "speed": 10000 }, "eth1": { "speed": 1000 }, "eth2": { "speed": 100 } } ``` -The user specifies allocation percentages (totaling `1.0`). In this case quality measurements will only be used to determine a slave's eligibility to be a member of a bond, now how much traffic it will carry: +The user specifies allocation percentages (totaling `1.0`). In this case quality measurements will only be used to determine a link's eligibility to be a member of a bond, now how much traffic it will carry: ``` -"slaves": { +"links": { "eth0": { "alloc": 0.50 }, "eth1": { "alloc": 0.25 }, "eth2": { "alloc": 0.25 } } ``` -#### Performance and Overhead Considerations +#### Performance and overhead considerations - Only packets with internal IDs divisible by `16` are included in measurements, this amounts to about `6.25%` of all traffic. - `failoverInterval` specifies how quickly failover should occur during a link failure. In order to accomplish this a combination of active and passive measurement techniques are employed which may result in `VERB_HELLO` probes being sent every `failoverInterval / 4` time units. As a mitigation `monitorStrategy` may be set to `dynamic` so that probe frequency directly correlates with native application traffic. diff --git a/service/OneService.cpp b/service/OneService.cpp index ec24f7ade..7f73e903f 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -50,7 +50,7 @@ #include "../osdep/Binder.hpp" #include "../osdep/ManagedRoute.hpp" #include "../osdep/BlockingQueue.hpp" -#include "../osdep/Slave.hpp" +#include "../osdep/Link.hpp" #include "OneService.hpp" #include "SoftwareUpdater.hpp" @@ -307,7 +307,7 @@ static void _peerBondToJson(nlohmann::json &pj,const ZT_Peer *peer) //j["ifname"] = peer->paths[i].ifname; pa.push_back(j); } - pj["slaves"] = pa; + pj["links"] = pa; } static void _moonToJson(nlohmann::json &mj,const World &world) @@ -1623,58 +1623,61 @@ public: newTemplateBond->setDownDelay(OSUtils::jsonInt(customPolicy["downDelay"],-1)); newTemplateBond->setFlowRebalanceStrategy(OSUtils::jsonInt(customPolicy["flowRebalanceStrategy"],(uint64_t)0)); newTemplateBond->setFailoverInterval(OSUtils::jsonInt(customPolicy["failoverInterval"],(uint64_t)0)); - newTemplateBond->setPacketsPerSlave(OSUtils::jsonInt(customPolicy["packetsPerSlave"],-1)); - std::string slaveMonitorStrategyStr(OSUtils::jsonString(customPolicy["slaveMonitorStrategy"],"")); - uint8_t slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DEFAULT; - if (slaveMonitorStrategyStr == "passive") { newTemplateBond->setSlaveMonitorStrategy(ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_PASSIVE); } - if (slaveMonitorStrategyStr == "active") { newTemplateBond->setSlaveMonitorStrategy(ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_ACTIVE); } - if (slaveMonitorStrategyStr == "dynamic") { newTemplateBond->setSlaveMonitorStrategy(ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC); } - // Policy-Specific slave set - json &slaves = customPolicy["slaves"]; - for (json::iterator slaveItr = slaves.begin(); slaveItr != slaves.end();++slaveItr) { - fprintf(stderr, "\t--- slave (%s)\n", slaveItr.key().c_str()); - std::string slaveNameStr(slaveItr.key()); - json &slave = slaveItr.value(); + newTemplateBond->setPacketsPerLink(OSUtils::jsonInt(customPolicy["packetsPerLink"],-1)); - bool enabled = OSUtils::jsonInt(slave["enabled"],true); - uint32_t speed = OSUtils::jsonInt(slave["speed"],0); - float alloc = (float)OSUtils::jsonDouble(slave["alloc"],0); + std::string linkMonitorStrategyStr(OSUtils::jsonString(customPolicy["linkMonitorStrategy"],"")); + uint8_t linkMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DEFAULT; + if (linkMonitorStrategyStr == "passive") { linkMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_PASSIVE; } + if (linkMonitorStrategyStr == "active") { linkMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_ACTIVE; } + if (linkMonitorStrategyStr == "dynamic") { linkMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; } + newTemplateBond->setLinkMonitorStrategy(linkMonitorStrategy); + + // Policy-Specific link set + json &links = customPolicy["links"]; + for (json::iterator linkItr = links.begin(); linkItr != links.end();++linkItr) { + fprintf(stderr, "\t--- link (%s)\n", linkItr.key().c_str()); + std::string linkNameStr(linkItr.key()); + json &link = linkItr.value(); + + bool enabled = OSUtils::jsonInt(link["enabled"],true); + uint32_t speed = OSUtils::jsonInt(link["speed"],0); + float alloc = (float)OSUtils::jsonDouble(link["alloc"],0); if (speed && alloc) { - fprintf(stderr, "error: cannot specify both speed (%d) and alloc (%f) for slave (%s), pick one, slave disabled.\n", - speed, alloc, slaveNameStr.c_str()); + fprintf(stderr, "error: cannot specify both speed (%d) and alloc (%f) for link (%s), pick one, link disabled.\n", + speed, alloc, linkNameStr.c_str()); enabled = false; } - uint32_t upDelay = OSUtils::jsonInt(slave["upDelay"],-1); - uint32_t downDelay = OSUtils::jsonInt(slave["downDelay"],-1); - uint8_t ipvPref = OSUtils::jsonInt(slave["ipvPref"],0); - uint32_t slaveMonitorInterval = OSUtils::jsonInt(slave["monitorInterval"],(uint64_t)0); - std::string failoverToStr(OSUtils::jsonString(slave["failoverTo"],"")); + uint32_t upDelay = OSUtils::jsonInt(link["upDelay"],-1); + uint32_t downDelay = OSUtils::jsonInt(link["downDelay"],-1); + uint8_t ipvPref = OSUtils::jsonInt(link["ipvPref"],0); + uint32_t linkMonitorInterval = OSUtils::jsonInt(link["monitorInterval"],(uint64_t)0); + std::string failoverToStr(OSUtils::jsonString(link["failoverTo"],"")); // Mode - std::string slaveModeStr(OSUtils::jsonString(slave["mode"],"spare")); - uint8_t slaveMode = ZT_MULTIPATH_SLAVE_MODE_SPARE; - if (slaveModeStr == "primary") { slaveMode = ZT_MULTIPATH_SLAVE_MODE_PRIMARY; } - if (slaveModeStr == "spare") { slaveMode = ZT_MULTIPATH_SLAVE_MODE_SPARE; } + std::string linkModeStr(OSUtils::jsonString(link["mode"],"spare")); + uint8_t linkMode = ZT_MULTIPATH_SLAVE_MODE_SPARE; + if (linkModeStr == "primary") { linkMode = ZT_MULTIPATH_SLAVE_MODE_PRIMARY; } + if (linkModeStr == "spare") { linkMode = ZT_MULTIPATH_SLAVE_MODE_SPARE; } // ipvPref if ((ipvPref != 0) && (ipvPref != 4) && (ipvPref != 6) && (ipvPref != 46) && (ipvPref != 64)) { - fprintf(stderr, "error: invalid ipvPref value (%d), slave disabled.\n", ipvPref); + fprintf(stderr, "error: invalid ipvPref value (%d), link disabled.\n", ipvPref); enabled = false; } - if (slaveMode == ZT_MULTIPATH_SLAVE_MODE_SPARE && failoverToStr.length()) { - fprintf(stderr, "error: cannot specify failover slaves for spares, slave disabled.\n"); + if (linkMode == ZT_MULTIPATH_SLAVE_MODE_SPARE && failoverToStr.length()) { + fprintf(stderr, "error: cannot specify failover links for spares, link disabled.\n"); failoverToStr = ""; enabled = false; } - _node->bondController()->addCustomSlave(customPolicyStr, new Slave(slaveNameStr,ipvPref,speed,slaveMonitorInterval,upDelay,downDelay,enabled,slaveMode,failoverToStr,alloc)); + _node->bondController()->addCustomLink(customPolicyStr, new Link(linkNameStr,ipvPref,speed,linkMonitorInterval,upDelay,downDelay,enabled,linkMode,failoverToStr,alloc)); } // TODO: This is dumb - std::string slaveSelectMethodStr(OSUtils::jsonString(customPolicy["activeReselect"],"optimize")); - if (slaveSelectMethodStr == "always") { newTemplateBond->setSlaveSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_ALWAYS); } - if (slaveSelectMethodStr == "better") { newTemplateBond->setSlaveSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_BETTER); } - if (slaveSelectMethodStr == "failure") { newTemplateBond->setSlaveSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_FAILURE); } - if (slaveSelectMethodStr == "optimize") { newTemplateBond->setSlaveSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE); } - if (newTemplateBond->getSlaveSelectMethod() < 0 || newTemplateBond->getSlaveSelectMethod() > 3) { - fprintf(stderr, "warning: invalid value (%s) for slaveSelectMethod, assuming mode: always\n", slaveSelectMethodStr.c_str()); + std::string linkSelectMethodStr(OSUtils::jsonString(customPolicy["activeReselect"],"optimize")); + if (linkSelectMethodStr == "always") { newTemplateBond->setLinkSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_ALWAYS); } + if (linkSelectMethodStr == "better") { newTemplateBond->setLinkSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_BETTER); } + if (linkSelectMethodStr == "failure") { newTemplateBond->setLinkSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_FAILURE); } + if (linkSelectMethodStr == "optimize") { newTemplateBond->setLinkSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE); } + if (newTemplateBond->getLinkSelectMethod() < 0 || newTemplateBond->getLinkSelectMethod() > 3) { + fprintf(stderr, "warning: invalid value (%s) for linkSelectMethod, assuming mode: always\n", linkSelectMethodStr.c_str()); } /* newBond->setPolicy(_node->bondController()->getPolicyCodeByStr(basePolicyStr)); @@ -1693,7 +1696,7 @@ public: } // Check settings if (defaultBondingPolicyStr.length() && !defaultBondingPolicy && !_node->bondController()->inUse()) { - fprintf(stderr, "error: unknown policy (%s) specified by defaultBondingPolicy, slave disabled.\n", defaultBondingPolicyStr.c_str()); + fprintf(stderr, "error: unknown policy (%s) specified by defaultBondingPolicy, link disabled.\n", defaultBondingPolicyStr.c_str()); } } From 1bb1dfa87bb82ee4ece3752a93d76c0f63144221 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 18 Jun 2020 09:32:00 -0700 Subject: [PATCH 097/362] android changes --- .../zerotier/sdk/VirtualNetworkConfig.java | 97 ++++++++++++++++--- 1 file changed, 82 insertions(+), 15 deletions(-) diff --git a/java/src/com/zerotier/sdk/VirtualNetworkConfig.java b/java/src/com/zerotier/sdk/VirtualNetworkConfig.java index 0e1945dfa..bb4e07110 100644 --- a/java/src/com/zerotier/sdk/VirtualNetworkConfig.java +++ b/java/src/com/zerotier/sdk/VirtualNetworkConfig.java @@ -27,6 +27,8 @@ package com.zerotier.sdk; +import android.util.Log; + import java.lang.Comparable; import java.lang.Override; import java.lang.String; @@ -35,6 +37,8 @@ import java.net.InetSocketAddress; import java.util.Collections; public final class VirtualNetworkConfig implements Comparable { + private final static String TAG = "VirtualNetworkConfig"; + public static final int MAX_MULTICAST_SUBSCRIPTIONS = 4096; public static final int ZT_MAX_ZT_ASSIGNED_ADDRESSES = 16; @@ -58,30 +62,93 @@ public final class VirtualNetworkConfig implements Comparable current = new ArrayList<>(); - ArrayList newConfig = new ArrayList<>(); + ArrayList aaCurrent = new ArrayList<>(); + ArrayList aaNew = new ArrayList<>(); for (InetSocketAddress s : assignedAddresses) { - current.add(s.toString()); + aaCurrent.add(s.toString()); } for (InetSocketAddress s : cfg.assignedAddresses) { - newConfig.add(s.toString()); + aaNew.add(s.toString()); } - Collections.sort(current); - Collections.sort(newConfig); - boolean aaEqual = current.equals(newConfig); - - current.clear(); - newConfig.clear(); + Collections.sort(aaCurrent); + Collections.sort(aaNew); + boolean aaEqual = aaCurrent.equals(aaNew); + ArrayList rCurrent = new ArrayList<>(); + ArrayList rNew = new ArrayList<>(); for (VirtualNetworkRoute r : routes) { - current.add(r.toString()); + rCurrent.add(r.toString()); } for (VirtualNetworkRoute r : cfg.routes) { - newConfig.add(r.toString()); + rNew.add(r.toString()); + } + Collections.sort(rCurrent); + Collections.sort(rNew); + boolean routesEqual = rCurrent.equals(rNew); + + if (this.nwid != cfg.nwid) { + Log.i(TAG, "nwid Changed. Old: " + Long.toHexString(this.nwid) + " (" + Long.toString(this.nwid) + "), " + + "New: " + Long.toHexString(cfg.nwid) + " (" + Long.toString(cfg.nwid) + ")"); + } + if (this.mac != cfg.mac) { + Log.i(TAG, "MAC Changed. Old: " + Long.toHexString(this.mac) + ", New: " + Long.toHexString(cfg.mac)); + } + + if (!this.name.equals(cfg.name)) { + Log.i(TAG, "Name Changed. Old: " + this.name + " New: "+ cfg.name); + } + + if (!this.type.equals(cfg.type)) { + Log.i(TAG, "TYPE changed. Old " + this.type + ", New: " + cfg.type); + } + + if (this.mtu != cfg.mtu) { + Log.i(TAG, "MTU Changed. Old: " + this.mtu + ", New: " + cfg.mtu); + } + + if (this.dhcp != cfg.dhcp) { + Log.i(TAG, "DHCP Flag Changed. Old: " + this.dhcp + ", New: " + cfg.dhcp); + } + + if (this.bridge != cfg.bridge) { + Log.i(TAG, "Bridge Flag Changed. Old: " + this.bridge + ", New: " + cfg.bridge); + } + + if (this.broadcastEnabled != cfg.broadcastEnabled) { + Log.i(TAG, "Broadcast Flag Changed. Old: "+ this.broadcastEnabled +", New: " + this.broadcastEnabled); + } + + if (this.portError != cfg.portError) { + Log.i(TAG, "Port Error Changed. Old: " + this.portError + ", New: " + this.portError); + } + + if (this.enabled != cfg.enabled) { + Log.i(TAG, "Enabled Changed. Old: " + this.enabled + ", New: " + this.enabled); + } + + if (!aaEqual) { + Log.i(TAG, "Assigned Addresses Changed"); + Log.i(TAG, "Old:"); + for (String s : aaCurrent) { + Log.i(TAG, " " + s); + } + Log.i(TAG, "New:"); + for (String s : aaNew) { + Log.i(TAG, " " +s); + } + } + + if (!routesEqual) { + Log.i(TAG, "Managed Routes Changed"); + Log.i(TAG, "Old:"); + for (String s : rCurrent) { + Log.i(TAG, " " + s); + } + Log.i(TAG, "New:"); + for (String s : rNew) { + Log.i(TAG, " " + s); + } } - Collections.sort(current); - Collections.sort(newConfig); - boolean routesEqual = current.equals(newConfig); return this.nwid == cfg.nwid && this.mac == cfg.mac && From 29e7fa5c4be1a320679875231a5cb98906a4c4fe Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 6 Jul 2020 14:07:31 -0700 Subject: [PATCH 098/362] Revert to ancient path redundancy check logic --- node/Peer.cpp | 55 ++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/node/Peer.cpp b/node/Peer.cpp index 565118867..f99396aaa 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -115,45 +115,50 @@ void Peer::received( } bool attemptToContact = false; - - int replaceIdx = ZT_MAX_PEER_NETWORK_PATHS; if ((!havePath)&&(RR->node->shouldUsePathForZeroTierTraffic(tPtr,_id.address(),path->localSocket(),path->address()))) { Mutex::Lock _l(_paths_m); + + // Paths are redunant if they duplicate an alive path to the same IP or + // with the same local socket and address family. + bool redundant = false; for(unsigned int i=0;ialive(now)) && ( ((_paths[i].p->localSocket() == path->localSocket())&&(_paths[i].p->address().ss_family == path->address().ss_family)) && (_paths[i].p->address().ipsEqual2(path->address())) ) ) { - // port - if (_paths[i].p->address().port() == path->address().port()) { - replaceIdx = i; - break; - } + if ( (_paths[i].p->alive(now)) && ( ((_paths[i].p->localSocket() == path->localSocket())&&(_paths[i].p->address().ss_family == path->address().ss_family)) || (_paths[i].p->address().ipsEqual2(path->address())) ) ) { + redundant = true; + break; } - } + } else break; } - if (replaceIdx == ZT_MAX_PEER_NETWORK_PATHS) { + + if (!redundant) { + unsigned int replacePath = ZT_MAX_PEER_NETWORK_PATHS; + int replacePathQuality = 0; for(unsigned int i=0;iquality(now); + if (q > replacePathQuality) { + replacePathQuality = q; + replacePath = i; + } + } else { + replacePath = i; break; } } - } - if (replaceIdx != ZT_MAX_PEER_NETWORK_PATHS) { - if (verb == Packet::VERB_OK) { - RR->t->peerLearnedNewPath(tPtr,networkId,*this,path,packetId); - performMultipathStateCheck(now); - if (_bondToPeer) { - _bondToPeer->nominatePath(path, now); + + if (replacePath != ZT_MAX_PEER_NETWORK_PATHS) { + if (verb == Packet::VERB_OK) { + RR->t->peerLearnedNewPath(tPtr,networkId,*this,path,packetId); + _paths[replacePath].lr = now; + _paths[replacePath].p = path; + _paths[replacePath].priority = 1; + } else { + attemptToContact = true; } - _paths[replaceIdx].lr = now; - _paths[replaceIdx].p = path; - _paths[replaceIdx].priority = 1; - } else { - attemptToContact = true; } } } + if (attemptToContact) { attemptToContactAt(tPtr,path->localSocket(),path->address(),now,true); path->sent(now); From bd6c97aeb8cfc11b597217d6fe515075e49011d8 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 6 Jul 2020 14:35:05 -0700 Subject: [PATCH 099/362] dont strip binary --- make-mac.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make-mac.mk b/make-mac.mk index f7ff8759b..ada65ff77 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -99,7 +99,7 @@ mac-agent: FORCE one: $(CORE_OBJS) $(ONE_OBJS) one.o mac-agent $(CXX) $(CXXFLAGS) -o zerotier-one $(CORE_OBJS) $(ONE_OBJS) one.o $(LIBS) - $(STRIP) zerotier-one + # $(STRIP) zerotier-one ln -sf zerotier-one zerotier-idtool ln -sf zerotier-one zerotier-cli $(CODESIGN) -f -s $(CODESIGN_APP_CERT) zerotier-one From 8eb453e04255eee672d881756453fc58ee588217 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 6 Jul 2020 15:18:17 -0700 Subject: [PATCH 100/362] Revert to ancient path redundancy check logic --- node/Peer.cpp | 52 ++++++++++++++------------------------------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/node/Peer.cpp b/node/Peer.cpp index c70e89d71..c30f571b8 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -93,28 +93,11 @@ void Peer::received( break; } - if (trustEstablished) { + if (trustEstablished) { _lastTrustEstablishedPacketReceived = now; path->trustedPacketReceived(now); } - { - Mutex::Lock _l(_paths_m); - - recordIncomingPacket(tPtr, path, packetId, payloadLength, verb, now); - - if (_canUseMultipath) { - if (path->needsToSendQoS(now)) { - sendQOS_MEASUREMENT(tPtr, path, path->localSocket(), path->address(), now); - } - for(unsigned int i=0;iprocessBackgroundPathMeasurements(now); - } - } - } - } - if (hops == 0) { // If this is a direct packet (no hops), update existing paths or learn new ones bool havePath = false; @@ -135,27 +118,20 @@ void Peer::received( if ((!havePath)&&(RR->node->shouldUsePathForZeroTierTraffic(tPtr,_id.address(),path->localSocket(),path->address()))) { Mutex::Lock _l(_paths_m); - // Paths are redundant if they duplicate an alive path to the same IP or + // Paths are redunant if they duplicate an alive path to the same IP or // with the same local socket and address family. bool redundant = false; - unsigned int replacePath = ZT_MAX_PEER_NETWORK_PATHS; for(unsigned int i=0;ialive(now)) && ( ((_paths[i].p->localSocket() == path->localSocket())&&(_paths[i].p->address().ss_family == path->address().ss_family)) || (_paths[i].p->address().ipsEqual2(path->address())) ) ) { + if ( (_paths[i].p->alive(now)) && ( ((_paths[i].p->localSocket() == path->localSocket())&&(_paths[i].p->address().ss_family == path->address().ss_family)) || (_paths[i].p->address().ipsEqual2(path->address())) ) ) { redundant = true; break; } - // If the path is the same address and port, simply assume this is a replacement - if ( (_paths[i].p->address().ipsEqual2(path->address()))) { - replacePath = i; - break; - } } else break; } - // If the path isn't a duplicate of the same localSocket AND we haven't already determined a replacePath, - // then find the worst path and replace it. - if (!redundant && replacePath == ZT_MAX_PEER_NETWORK_PATHS) { + if (!redundant) { + unsigned int replacePath = ZT_MAX_PEER_NETWORK_PATHS; int replacePathQuality = 0; for(unsigned int i=0;it->peerLearnedNewPath(tPtr,networkId,*this,path,packetId); - _paths[replacePath].lr = now; - _paths[replacePath].p = path; - _paths[replacePath].priority = 1; - } else { - attemptToContact = true; + if (replacePath != ZT_MAX_PEER_NETWORK_PATHS) { + if (verb == Packet::VERB_OK) { + RR->t->peerLearnedNewPath(tPtr,networkId,*this,path,packetId); + _paths[replacePath].lr = now; + _paths[replacePath].p = path; + _paths[replacePath].priority = 1; + } else { + attemptToContact = true; + } } } } From decd5add2a9ee1302f23cb901518b9c5262fc99c Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 14 Jul 2020 17:05:05 -0700 Subject: [PATCH 101/362] Fix segfault on unique interface name list generation (for multipath) --- osdep/Binder.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osdep/Binder.hpp b/osdep/Binder.hpp index 8f572a4f5..8076b6e92 100644 --- a/osdep/Binder.hpp +++ b/osdep/Binder.hpp @@ -348,10 +348,11 @@ public: } // Generate set of unique interface names (used for formation of logical link set in multipath code) + // TODO: Could be gated not to run if multipath is not enabled. for(std::map::const_iterator ii(localIfAddrs.begin());ii!=localIfAddrs.end();++ii) { linkIfNames.insert(ii->second); } - for (std::set::iterator si(linkIfNames.begin());si!=linkIfNames.end();si++) { + for (std::set::iterator si(linkIfNames.begin());si!=linkIfNames.end();) { bool bFoundMatch = false; for(std::map::const_iterator ii(localIfAddrs.begin());ii!=localIfAddrs.end();++ii) { if (ii->second == *si) { @@ -360,7 +361,10 @@ public: } } if (!bFoundMatch) { - linkIfNames.erase(si); + linkIfNames.erase(si++); + } + else { + ++si; } } From ffebcd247fcc5fec9aee85c26da581cee8a09d72 Mon Sep 17 00:00:00 2001 From: Suad Halilovic Date: Thu, 16 Jul 2020 18:31:56 +0200 Subject: [PATCH 102/362] 2020/07/16, Minor optmizations --- CMakeLists.txt | 1 + controller/EmbeddedNetworkController.cpp | 14 +++++++------- controller/LFDB.cpp | 8 ++++---- node/IncomingPacket.cpp | 2 +- node/Peer.cpp | 2 +- node/Topology.cpp | 4 ++-- one.cpp | 8 ++++---- osdep/Binder.hpp | 4 ++-- osdep/OSUtils.cpp | 2 +- service/OneService.cpp | 18 +++++++++--------- service/SoftwareUpdater.cpp | 4 ++-- 11 files changed, 34 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fff7808e1..b22ae7d77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ project (zerotiercore) set (PROJ_DIR ${PROJECT_SOURCE_DIR}) set (ZT_DEFS -std=c++11) +set (CMAKE_EXPORT_COMPILE_COMMANDS ON) file(GLOB core_src_glob ${PROJ_DIR}/node/*.cpp) add_library(zerotiercore STATIC ${core_src_glob}) diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index e0e2a3eae..e5db6eca1 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -97,7 +97,7 @@ static json _renderRule(ZT_VirtualNetworkRule &rule) break; } - if (r.size() == 0) { + if (r.empty()) { switch(rt) { case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: r["type"] = "MATCH_SOURCE_ZEROTIER_ADDRESS"; @@ -239,7 +239,7 @@ static json _renderRule(ZT_VirtualNetworkRule &rule) break; } - if (r.size() > 0) { + if (!r.empty()) { r["not"] = ((rule.t & 0x80) != 0); r["or"] = ((rule.t & 0x40) != 0); } @@ -554,7 +554,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET( std::string &responseBody, std::string &responseContentType) { - if ((path.size() > 0)&&(path[0] == "network")) { + if ((!path.empty())&&(path[0] == "network")) { if ((path.size() >= 2)&&(path[1].length() == 16)) { const uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); @@ -1227,11 +1227,11 @@ void EmbeddedNetworkController::_request( Utils::hex(nwid,nwids); _db.get(nwid,network,identity.address().toInt(),member,ns); - if ((!network.is_object())||(network.size() == 0)) { + if ((!network.is_object())||(network.empty())) { _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_OBJECT_NOT_FOUND); return; } - const bool newMember = ((!member.is_object())||(member.size() == 0)); + const bool newMember = ((!member.is_object())||(member.empty())); DB::initMember(member); { @@ -1437,11 +1437,11 @@ void EmbeddedNetworkController::_request( std::map< uint64_t,json * >::const_iterator ctmp = capsById.find(capId); if (ctmp != capsById.end()) { json *cap = ctmp->second; - if ((cap)&&(cap->is_object())&&(cap->size() > 0)) { + if ((cap)&&(cap->is_object())&&(!cap->empty())) { ZT_VirtualNetworkRule capr[ZT_MAX_CAPABILITY_RULES]; unsigned int caprc = 0; json &caprj = (*cap)["rules"]; - if ((caprj.is_array())&&(caprj.size() > 0)) { + if ((caprj.is_array())&&(!caprj.empty())) { for(unsigned long j=0;j= ZT_MAX_CAPABILITY_RULES) break; diff --git a/controller/LFDB.cpp b/controller/LFDB.cpp index d11b77a07..03265347f 100644 --- a/controller/LFDB.cpp +++ b/controller/LFDB.cpp @@ -190,10 +190,10 @@ LFDB::LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,cons if (resp) { if (resp->status == 200) { nlohmann::json results(OSUtils::jsonParse(resp->body)); - if ((results.is_array())&&(results.size() > 0)) { + if ((results.is_array())&&(!results.empty())) { for(std::size_t ri=0;ri 0)) { + if ((rset.is_array())&&(!rset.empty())) { nlohmann::json &result = rset[0]; if (result.is_object()) { @@ -258,10 +258,10 @@ LFDB::LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,cons if (resp) { if (resp->status == 200) { nlohmann::json results(OSUtils::jsonParse(resp->body)); - if ((results.is_array())&&(results.size() > 0)) { + if ((results.is_array())&&(!results.empty())) { for(std::size_t ri=0;ri 0)) { + if ((rset.is_array())&&(!rset.empty())) { nlohmann::json &result = rset[0]; if (result.is_object()) { diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 331446ced..d6f6d951c 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -427,7 +427,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool if ((planetWorldId)&&(RR->topology->planetWorldTimestamp() > planetWorldTimestamp)&&(planetWorldId == RR->topology->planetWorldId())) { RR->topology->planet().serialize(outp,false); } - if (moonIdsAndTimestamps.size() > 0) { + if (!moonIdsAndTimestamps.empty()) { std::vector moons(RR->topology->moons()); for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { for(std::vector< std::pair >::const_iterator i(moonIdsAndTimestamps.begin());i!=moonIdsAndTimestamps.end();++i) { diff --git a/node/Peer.cpp b/node/Peer.cpp index c70e89d71..35b35559f 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -198,7 +198,7 @@ void Peer::received( if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH : ZT_DIRECT_PATH_PUSH_INTERVAL)) { _lastDirectPathPushSent = now; std::vector pathsToPush(RR->node->directPaths()); - if (pathsToPush.size() > 0) { + if (!pathsToPush.empty()) { std::vector::const_iterator p(pathsToPush.begin()); while (p != pathsToPush.end()) { Packet *const outp = new Packet(_id.address(),RR->identity.address(),Packet::VERB_PUSH_DIRECT_PATHS); diff --git a/node/Topology.cpp b/node/Topology.cpp index 01a81fccc..ab70220a1 100644 --- a/node/Topology.cpp +++ b/node/Topology.cpp @@ -183,7 +183,7 @@ bool Topology::isProhibitedEndpoint(const Address &ztaddr,const InetAddress &ipa if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),ztaddr) != _upstreamAddresses.end()) { for(std::vector::const_iterator r(_planet.roots().begin());r!=_planet.roots().end();++r) { if (r->identity.address() == ztaddr) { - if (r->stableEndpoints.size() == 0) + if (r->stableEndpoints.empty()) return false; // no stable endpoints specified, so allow dynamic paths for(std::vector::const_iterator e(r->stableEndpoints.begin());e!=r->stableEndpoints.end();++e) { if (ipaddr.ipsEqual(*e)) @@ -194,7 +194,7 @@ bool Topology::isProhibitedEndpoint(const Address &ztaddr,const InetAddress &ipa for(std::vector::const_iterator m(_moons.begin());m!=_moons.end();++m) { for(std::vector::const_iterator r(m->roots().begin());r!=m->roots().end();++r) { if (r->identity.address() == ztaddr) { - if (r->stableEndpoints.size() == 0) + if (r->stableEndpoints.empty()) return false; // no stable endpoints specified, so allow dynamic paths for(std::vector::const_iterator e(r->stableEndpoints.begin());e!=r->stableEndpoints.end();++e) { if (ipaddr.ipsEqual(*e)) diff --git a/one.cpp b/one.cpp index 06d56e7ae..88731285a 100644 --- a/one.cpp +++ b/one.cpp @@ -739,7 +739,7 @@ static int cli(int argc,char **argv) int addressCountOfType = 0; for (int k = 0; k().find(".") != std::string::npos) + if ((arg2 == "ip4" && addr.get().find('.') != std::string::npos) || ((arg2.find("ip6") == 0) && addr.get().find(":") != std::string::npos) || (arg2 == "ip") ) { @@ -754,19 +754,19 @@ static int cli(int argc,char **argv) if (arg2.find("ip6p") == 0) { if (arg2 == "ip6plane") { if (addr.get().find("fc") == 0) { - aa.append(addr.get().substr(0,addr.get().find("/"))); + aa.append(addr.get().substr(0,addr.get().find('/'))); if (k < addressCountOfType-1) aa.append("\n"); } } if (arg2 == "ip6prefix") { if (addr.get().find("fc") == 0) { - aa.append(addr.get().substr(0,addr.get().find("/")).substr(0,24)); + aa.append(addr.get().substr(0,addr.get().find('/')).substr(0,24)); if (k < addressCountOfType-1) aa.append("\n"); } } } else { - aa.append(addr.get().substr(0,addr.get().find("/"))); + aa.append(addr.get().substr(0,addr.get().find('/'))); if (k < addressCountOfType-1) aa.append("\n"); } } diff --git a/osdep/Binder.hpp b/osdep/Binder.hpp index 660e6f0c3..1d703d810 100644 --- a/osdep/Binder.hpp +++ b/osdep/Binder.hpp @@ -234,7 +234,7 @@ public: } // Get IPv4 addresses for each device - if (ifnames.size() > 0) { + if (!ifnames.empty()) { const int controlfd = (int)socket(AF_INET,SOCK_DGRAM,0); struct ifconf configuration; configuration.ifc_len = 0; @@ -276,7 +276,7 @@ public: if (controlfd > 0) close(controlfd); } - const bool gotViaProc = (localIfAddrs.size() > 0); + const bool gotViaProc = (!localIfAddrs.empty()); #else const bool gotViaProc = false; #endif diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp index 0299b12bc..4d9a2bb5e 100644 --- a/osdep/OSUtils.cpp +++ b/osdep/OSUtils.cpp @@ -351,7 +351,7 @@ std::vector OSUtils::split(const char *s,const char *const sep,cons else if ((buf.size() <= 0)&&((quotTmp = strchr(quot,*s)))) quoteState = *quotTmp; else if (strchr(sep,*s)) { - if (buf.size() > 0) { + if (!buf.empty()) { fields.push_back(buf); buf.clear(); } // else skip runs of separators diff --git a/service/OneService.cpp b/service/OneService.cpp index 97ba1a362..e5da3eabe 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -1016,7 +1016,7 @@ public: } // Set trusted paths if there are any - if (ppc.size() > 0) { + if (!ppc.empty()) { for(std::map::iterator i(ppc.begin());i!=ppc.end();++i) _node->setPhysicalPathConfiguration(reinterpret_cast(&(i->first)),&(i->second)); } @@ -1132,7 +1132,7 @@ public: * URL encoding, and /'s in URL args will screw it up. But the only URL args * it really uses in ?jsonp=funcionName, and otherwise it just takes simple * paths to simply-named resources. */ - if (ps.size() > 0) { + if (!ps.empty()) { std::size_t qpos = ps[ps.size() - 1].find('?'); if (qpos != std::string::npos) { std::string args(ps[ps.size() - 1].substr(qpos + 1)); @@ -1165,12 +1165,12 @@ public: // Authenticate via Synology's built-in cgi script if (!isAuth) { int synotoken_pos = path.find("SynoToken"); - int argpos = path.find("?"); + int argpos = path.find('?'); if(synotoken_pos != std::string::npos && argpos != std::string::npos) { std::string cookie = path.substr(argpos+1, synotoken_pos-(argpos+1)); std::string synotoken = path.substr(synotoken_pos); - std::string cookie_val = cookie.substr(cookie.find("=")+1); - std::string synotoken_val = synotoken.substr(synotoken.find("=")+1); + std::string cookie_val = cookie.substr(cookie.find('=')+1); + std::string synotoken_val = synotoken.substr(synotoken.find('=')+1); // Set necessary env for auth script std::map::const_iterator ah2(headers.find("x-forwarded-for")); setenv("HTTP_COOKIE", cookie_val.c_str(), true); @@ -1661,7 +1661,7 @@ public: if (!n.settings.allowManaged) return false; - if (n.settings.allowManagedWhitelist.size() > 0) { + if (!n.settings.allowManagedWhitelist.empty()) { bool allowed = false; for (InetAddress addr : n.settings.allowManagedWhitelist) { if (addr.containsAddress(target) && addr.netmaskBits() <= target.netmaskBits()) { @@ -1932,7 +1932,7 @@ public: bool allow; { Mutex::Lock _l(_localConfig_m); - if (_allowManagementFrom.size() == 0) { + if (_allowManagementFrom.empty()) { allow = (tc->remoteAddr.ipScope() == InetAddress::IP_SCOPE_LOOPBACK); } else { allow = false; @@ -2113,7 +2113,7 @@ public: Dictionary<4096> nc; nc.load(nlcbuf.c_str()); Buffer<1024> allowManaged; - if (nc.get("allowManaged", allowManaged) && allowManaged.size() != 0) { + if (nc.get("allowManaged", allowManaged) && !allowManaged.empty()) { std::string addresses (allowManaged.begin(), allowManaged.size()); if (allowManaged.size() <= 5) { // untidy parsing for backward compatibility if (allowManaged[0] == '1' || allowManaged[0] == 't' || allowManaged[0] == 'T') { @@ -2671,7 +2671,7 @@ public: lh = &_v6Hints; else return 0; const std::vector *l = lh->get(ztaddr); - if ((l)&&(l->size() > 0)) { + if ((l)&&(!l->empty())) { memcpy(result,&((*l)[(unsigned long)_node->prng() % l->size()]),sizeof(struct sockaddr_storage)); return 1; } else return 0; diff --git a/service/SoftwareUpdater.cpp b/service/SoftwareUpdater.cpp index 5800f860f..cec2c6c5c 100644 --- a/service/SoftwareUpdater.cpp +++ b/service/SoftwareUpdater.cpp @@ -161,7 +161,7 @@ void SoftwareUpdater::handleSoftwareUpdateUserMessage(uint64_t origin,const void if (v == VERB_GET_LATEST) { - if (_dist.size() > 0) { + if (!_dist.empty()) { const nlohmann::json *latest = (const nlohmann::json *)0; const std::string expectedSigner = OSUtils::jsonString(req[ZT_SOFTWARE_UPDATE_JSON_EXPECT_SIGNED_BY],""); unsigned int bestVMaj = rvMaj; @@ -241,7 +241,7 @@ void SoftwareUpdater::handleSoftwareUpdateUserMessage(uint64_t origin,const void } break; case VERB_GET_DATA: - if ((len >= 21)&&(_dist.size() > 0)) { + if ((len >= 21)&&(!_dist.empty())) { unsigned long idx = (unsigned long)*(reinterpret_cast(data) + 17) << 24; idx |= (unsigned long)*(reinterpret_cast(data) + 18) << 16; idx |= (unsigned long)*(reinterpret_cast(data) + 19) << 8; From c1f4168d2f847161ca051b047c9fec91bbd10599 Mon Sep 17 00:00:00 2001 From: Suad Halilovic Date: Thu, 16 Jul 2020 18:38:04 +0200 Subject: [PATCH 103/362] 2020/07/16, Minor optmizations --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b22ae7d77..fff7808e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,6 @@ project (zerotiercore) set (PROJ_DIR ${PROJECT_SOURCE_DIR}) set (ZT_DEFS -std=c++11) -set (CMAKE_EXPORT_COMPILE_COMMANDS ON) file(GLOB core_src_glob ${PROJ_DIR}/node/*.cpp) add_library(zerotiercore STATIC ${core_src_glob}) From 387039456d9dd0a3a7b20017aaa71188c63903af Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 20 Jul 2020 14:34:19 -0700 Subject: [PATCH 104/362] Pass 1 at adding DNS to controller --- controller/EmbeddedNetworkController.cpp | 44 ++++++++++++++++++ include/ZeroTierOne.h | 29 ++++++++++++ node/DNS.hpp | 59 ++++++++++++++++++++++++ node/NetworkConfig.cpp | 16 +++++++ node/NetworkConfig.hpp | 17 ++++++- 5 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 node/DNS.hpp diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index b2bd7bfb9..b6621f29a 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -1029,6 +1029,30 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( } } + if (b.count("dns")) { + json &dns = b["dns"]; + if (dns.is_array()) { + json nda = json::array(); + for(unsigned int i=0;idnsCount = 0; + for(unsigned int p=0; p < dns.size(); ++p) { + json &d = dns[p]; + if (d.is_object()) { + std::string domain = OSUtils::jsonString(d["domain"],""); + memcpy(nc->dns[nc->dnsCount].domain, domain.c_str(), domain.size()); + json &addrArray = d["servers"]; + if (addrArray.is_array()) { + for(unsigned int j = 0; j < addrArray.size() && j < ZT_MAX_DNS_SERVERS; ++j) { + json &addr = addrArray[j]; + nc->dns[nc->dnsCount].server_addr[j] = InetAddress(OSUtils::jsonString(addr,"").c_str()); + } + } + ++nc->dnsCount; + } + } + } // Issue a certificate of ownership for all static IPs if (nc->staticIpCount) { diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index c93b4dbf8..8ff9ba2f4 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -125,6 +125,11 @@ extern "C" { */ #define ZT_MAX_NETWORK_ROUTES 32 +/** + * Maximum number of pushed DNS configurations on a network + */ +#define ZT_MAX_NETWORK_DNS 32 + /** * Maximum number of statically assigned IP addresses per network endpoint using ZT address management (not DHCP) */ @@ -195,6 +200,11 @@ extern "C" { */ #define ZT_PATH_LINK_QUALITY_MAX 0xff +/** + * Maximum number of DNS servers per domain + */ +#define ZT_MAX_DNS_SERVERS 4 + /** * Packet characteristics flag: packet direction, 1 if inbound 0 if outbound */ @@ -984,6 +994,15 @@ typedef struct uint16_t metric; } ZT_VirtualNetworkRoute; +/** + * DNS configuration to be pushed on a virtual network + */ +typedef struct +{ + char domain[128]; + struct sockaddr_storage server_addr[ZT_MAX_DNS_SERVERS]; +} ZT_VirtualNetworkDNS; + /** * An Ethernet multicast group */ @@ -1198,6 +1217,16 @@ typedef struct uint64_t mac; /* MAC in lower 48 bits */ uint32_t adi; /* Additional distinguishing information, usually zero except for IPv4 ARP groups */ } multicastSubscriptions[ZT_MAX_MULTICAST_SUBSCRIPTIONS]; + + /** + * Number of ZT-pushed DNS configuraitons + */ + unsigned int dnsCount; + + /** + * Network specific DNS configuration + */ + ZT_VirtualNetworkDNS dns[ZT_MAX_NETWORK_DNS]; } ZT_VirtualNetworkConfig; /** diff --git a/node/DNS.hpp b/node/DNS.hpp new file mode 100644 index 000000000..ff685d32d --- /dev/null +++ b/node/DNS.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (c)2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2023-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +#ifndef ZT_DNS_HPP +#define ZT_DNS_HPP +#include +#include +#include + +#include "Buffer.hpp" +#include "InetAddress.hpp" +#include "../include/ZeroTierOne.h" + +namespace ZeroTier { + +/** + * DNS data serealization methods + */ +class DNS { +public: + template + static inline void serializeDNS(Buffer &b, const ZT_VirtualNetworkDNS *dns, unsigned int dnsCount) + { + for(unsigned int i = 0; i < dnsCount; ++i) { + b.append(dns[i].domain, 128); + for(unsigned int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { + InetAddress tmp(dns[i].server_addr[j]); + tmp.serialize(b); + } + } + } + + template + static inline void deserializeDNS(const Buffer &b, unsigned int &p, ZT_VirtualNetworkDNS *dns, const unsigned int dnsCount) + { + for(unsigned int i = 0; i < dnsCount; ++i) { + char *d = (char*)b.data()+p; + memcpy(dns[i].domain, d, 128); + p += 128; + for (unsigned int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { + p += reinterpret_cast(&(dns[i].server_addr[j]))->deserialize(b, p); + } + } + } +}; + +} + +#endif // ZT_DNS_HPP diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index 97985c7af..503d7400a 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -176,6 +176,15 @@ bool NetworkConfig::toDictionary(Dictionary &d,b } } + tmp->clear(); + if (dnsCount > 0) { + tmp->append(dnsCount); + DNS::serializeDNS(*tmp, dns, dnsCount); + if (tmp->size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_DNS,*tmp)) return false; + } + } + delete tmp; } catch ( ... ) { delete tmp; @@ -354,6 +363,13 @@ bool NetworkConfig::fromDictionary(const Dictionaryrules,this->ruleCount,ZT_MAX_NETWORK_RULES); } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_DNS, *tmp)) { + unsigned int p = 0; + this->dnsCount = tmp->at(p); + p += sizeof(unsigned int); + DNS::deserializeDNS(*tmp, p, dns, (this->dnsCount <= ZT_MAX_NETWORK_DNS) ? this->dnsCount : ZT_MAX_NETWORK_DNS); + } } //printf("~~~\n%s\n~~~\n",d.data()); diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp index 8311a0743..1daf98d01 100644 --- a/node/NetworkConfig.hpp +++ b/node/NetworkConfig.hpp @@ -26,6 +26,7 @@ #include "Constants.hpp" #include "Buffer.hpp" +#include "DNS.hpp" #include "InetAddress.hpp" #include "MulticastGroup.hpp" #include "Address.hpp" @@ -175,6 +176,8 @@ namespace ZeroTier { #define ZT_NETWORKCONFIG_DICT_KEY_TAGS "TAG" // tags (binary blobs) #define ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP "COO" +// dns (binary blobs) +#define ZT_NETWORKCONFIG_DICT_KEY_DNS "DNS" // Legacy fields -- these are obsoleted but are included when older clients query @@ -229,13 +232,15 @@ public: capabilities(), tags(), certificatesOfOwnership(), - type(ZT_NETWORK_TYPE_PRIVATE) + type(ZT_NETWORK_TYPE_PRIVATE), + dnsCount(0) { name[0] = 0; memset(specialists, 0, sizeof(uint64_t)*ZT_MAX_NETWORK_SPECIALISTS); memset(routes, 0, sizeof(ZT_VirtualNetworkRoute)*ZT_MAX_NETWORK_ROUTES); memset(staticIps, 0, sizeof(InetAddress)*ZT_MAX_ZT_ASSIGNED_ADDRESSES); memset(rules, 0, sizeof(ZT_VirtualNetworkRule)*ZT_MAX_NETWORK_RULES); + memset(dns, 0, sizeof(ZT_VirtualNetworkDNS)*ZT_MAX_NETWORK_DNS); } /** @@ -589,6 +594,16 @@ public: * Certificate of membership (for private networks) */ CertificateOfMembership com; + + /** + * Number of ZT-pushed DNS configurations + */ + unsigned int dnsCount; + + /** + * ZT pushed DNS configuration + */ + ZT_VirtualNetworkDNS dns[ZT_MAX_NETWORK_DNS]; }; } // namespace ZeroTier From dc784f62131b8c2774e5f75944953c0b93a6c1d2 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 21 Jul 2020 10:22:10 -0700 Subject: [PATCH 105/362] Comment out Bond and BondController debug traces --- node/Bond.cpp | 202 ++++++++++++++++++++-------------------- node/BondController.cpp | 16 ++-- service/OneService.cpp | 4 +- 3 files changed, 113 insertions(+), 109 deletions(-) diff --git a/node/Bond.cpp b/node/Bond.cpp index 0338f5195..e96355ec9 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -54,7 +54,7 @@ Bond::Bond(const RuntimeEnvironment *renv, SharedPtr originalBond, const S void Bond::nominatePath(const SharedPtr& path, int64_t now) { - char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "nominatePath: %s %s\n", getLink(path)->ifname().c_str(), pathStr); + char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "nominatePath: %s %s\n", getLink(path)->ifname().c_str(), pathStr); Mutex::Lock _l(_paths_m); if (!RR->bc->linkAllowed(_policyAlias, getLink(path))) { return; @@ -62,7 +62,7 @@ void Bond::nominatePath(const SharedPtr& path, int64_t now) bool alreadyPresent = false; for (int i=0; i& path, int64_t now) if (!alreadyPresent) { for (int i=0; ilink = RR->bc->getLinkBySocket(_policyAlias, path->localSocket()); _paths[i]->startTrial(now); @@ -170,7 +170,7 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) void Bond::recordIncomingInvalidPacket(const SharedPtr& path) { - // char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordIncomingInvalidPacket() %s %s\n", getLink(path)->ifname().c_str(), pathStr); + // char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "recordIncomingInvalidPacket() %s %s\n", getLink(path)->ifname().c_str(), pathStr); Mutex::Lock _l(_paths_m); for (int i=0; i& path) void Bond::recordOutgoingPacket(const SharedPtr &path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now) { - // char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordOutgoingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getLink(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); + // char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "recordOutgoingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getLink(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); _freeRandomByte += (unsigned char)(packetId >> 8); // Grab entropy to use in path selection logic if (!_shouldCollectPathStatistics) { return; @@ -218,7 +218,7 @@ void Bond::recordOutgoingPacket(const SharedPtr &path, const uint64_t pack void Bond::recordIncomingPacket(const SharedPtr& path, uint64_t packetId, uint16_t payloadLength, Packet::Verb verb, int32_t flowId, int64_t now) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordIncomingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getLink(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); + //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "recordIncomingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getLink(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); bool isFrame = (verb == Packet::VERB_FRAME || verb == Packet::VERB_EXT_FRAME); bool shouldRecord = (packetId & (ZT_QOS_ACK_DIVISOR - 1) && (verb != Packet::VERB_ACK) @@ -261,7 +261,7 @@ void Bond::recordIncomingPacket(const SharedPtr& path, uint64_t packetId, void Bond::receivedQoS(const SharedPtr& path, int64_t now, int count, uint64_t *rx_id, uint16_t *rx_ts) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "receivedQoS() %s %s\n", getLink(path)->ifname().c_str(), pathStr); + //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "receivedQoS() %s %s\n", getLink(path)->ifname().c_str(), pathStr); Mutex::Lock _l(_paths_m); // Look up egress times and compute latency values for each record std::map::iterator it; @@ -273,13 +273,13 @@ void Bond::receivedQoS(const SharedPtr& path, int64_t now, int count, uint } } path->qosRecordSize.push(count); - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "receivedQoS() on path %s %s, count=%d, successful=%d, qosStatsOut.size()=%d\n", getLink(path)->ifname().c_str(), pathStr, count, path->aknowledgedQoSRecordCountSinceLastCheck, path->qosStatsOut.size()); + //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "receivedQoS() on path %s %s, count=%d, successful=%d, qosStatsOut.size()=%d\n", getLink(path)->ifname().c_str(), pathStr, count, path->aknowledgedQoSRecordCountSinceLastCheck, path->qosStatsOut.size()); } void Bond::receivedAck(const SharedPtr& path, int64_t now, int32_t ackedBytes) { Mutex::Lock _l(_paths_m); - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "receivedAck() %s %s, (ackedBytes=%d, lastAckReceived=%lld, ackAge=%lld)\n", getLink(path)->ifname().c_str(), pathStr, ackedBytes, path->lastAckReceived, path->ackAge(now)); + //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "receivedAck() %s %s, (ackedBytes=%d, lastAckReceived=%lld, ackAge=%lld)\n", getLink(path)->ifname().c_str(), pathStr, ackedBytes, path->lastAckReceived, path->ackAge(now)); path->_lastAckReceived = now; path->_unackedBytes = (ackedBytes > path->_unackedBytes) ? 0 : path->_unackedBytes - ackedBytes; int64_t timeSinceThroughputEstimate = (now - path->_lastThroughputEstimation); @@ -300,7 +300,7 @@ void Bond::receivedAck(const SharedPtr& path, int64_t now, int32_t ackedBy int32_t Bond::generateQoSPacket(const SharedPtr& path, int64_t now, char *qosBuffer) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "generateQoSPacket() %s %s\n", getLink(path)->ifname().c_str(), pathStr); + //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "generateQoSPacket() %s %s\n", getLink(path)->ifname().c_str(), pathStr); int32_t len = 0; std::map::iterator it = path->qosStatsIn.begin(); int i=0; @@ -337,7 +337,7 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) entropy %= _totalBondUnderload; } if (!_numBondedPaths) { - fprintf(stderr, "no bonded paths for flow assignment\n"); + //fprintf(stderr, "no bonded paths for flow assignment\n"); return false; } /* Since there may be scenarios where a path is removed before we can re-estimate @@ -375,13 +375,13 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) ++(_paths[idx]->_assignedFlowCount); } else { - fprintf(stderr, "could not assign flow?\n"); exit(0); // TODO: Remove for production + //fprintf(stderr, "could not assign flow?\n"); exit(0); // TODO: Remove for production return false; } } flow->assignedPath()->address().toString(curPathStr); SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, flow->assignedPath()->localSocket()); - fprintf(stderr, "assigned (tx) flow %x with peer %llx to path %s on %s (idx=%d)\n", flow->id(), _peer->_id.address().toInt(), curPathStr, link->ifname().c_str(), idx); + //fprintf(stderr, "assigned (tx) flow %x with peer %llx to path %s on %s (idx=%d)\n", flow->id(), _peer->_id.address().toInt(), curPathStr, link->ifname().c_str(), idx); return true; } @@ -391,16 +391,16 @@ SharedPtr Bond::createFlow(const SharedPtr &path, int32_t flowId, un char curPathStr[128]; // --- if (!_numBondedPaths) { - fprintf(stderr, "there are no bonded paths, cannot assign flow\n"); + //fprintf(stderr, "there are no bonded paths, cannot assign flow\n"); return SharedPtr(); } if (_flows.size() >= ZT_FLOW_MAX_COUNT) { - fprintf(stderr, "max number of flows reached (%d), forcibly forgetting oldest flow\n", ZT_FLOW_MAX_COUNT); + //fprintf(stderr, "max number of flows reached (%d), forcibly forgetting oldest flow\n", ZT_FLOW_MAX_COUNT); forgetFlowsWhenNecessary(0,true,now); } SharedPtr flow = new Flow(flowId, now); _flows[flowId] = flow; - fprintf(stderr, "new flow %x detected with peer %llx, %lu active flow(s)\n", flowId, _peer->_id.address().toInt(), (_flows.size())); + //fprintf(stderr, "new flow %x detected with peer %llx, %lu active flow(s)\n", flowId, _peer->_id.address().toInt(), (_flows.size())); /** * Add a flow with a given Path already provided. This is the case when a packet * is received on a path but no flow exists, in this case we simply assign the path @@ -411,7 +411,7 @@ SharedPtr Bond::createFlow(const SharedPtr &path, int32_t flowId, un path->address().toString(curPathStr); path->_assignedFlowCount++; SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, flow->assignedPath()->localSocket()); - fprintf(stderr, "assigned (rx) flow %x with peer %llx to path %s on %s\n", flow->id(), _peer->_id.address().toInt(), curPathStr, link->ifname().c_str()); + //fprintf(stderr, "assigned (rx) flow %x with peer %llx to path %s on %s\n", flow->id(), _peer->_id.address().toInt(), curPathStr, link->ifname().c_str()); } /** * Add a flow when no path was provided. This means that it is an outgoing packet @@ -432,7 +432,7 @@ void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) if (age) { // Remove by specific age while (it != _flows.end()) { if (it->second->age(now) > age) { - fprintf(stderr, "forgetting flow %x between this node and %llx, %lu active flow(s)\n", it->first, _peer->_id.address().toInt(), (_flows.size()-1)); + //fprintf(stderr, "forgetting flow %x between this node and %llx, %lu active flow(s)\n", it->first, _peer->_id.address().toInt(), (_flows.size()-1)); it->second->assignedPath()->_assignedFlowCount--; it = _flows.erase(it); } else { @@ -450,7 +450,7 @@ void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) ++it; } if (oldestFlow != _flows.end()) { - fprintf(stderr, "forgetting oldest flow %x (of age %llu) between this node and %llx, %lu active flow(s)\n", oldestFlow->first, oldestFlow->second->age(now), _peer->_id.address().toInt(), (_flows.size()-1)); + //fprintf(stderr, "forgetting oldest flow %x (of age %llu) between this node and %llx, %lu active flow(s)\n", oldestFlow->first, oldestFlow->second->age(now), _peer->_id.address().toInt(), (_flows.size()-1)); oldestFlow->second->assignedPath()->_assignedFlowCount--; _flows.erase(oldestFlow); } @@ -471,19 +471,19 @@ void Bond::processIncomingPathNegotiationRequest(uint64_t now, SharedPtr & } SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, path->localSocket()); if (remoteUtility > _localUtility) { - fprintf(stderr, "peer suggests path, its utility (%d) is greater than ours (%d), we will switch to %s on %s (ls=%llx)\n", remoteUtility, _localUtility, pathStr, link->ifname().c_str(), path->localSocket()); + //fprintf(stderr, "peer suggests path, its utility (%d) is greater than ours (%d), we will switch to %s on %s (ls=%llx)\n", remoteUtility, _localUtility, pathStr, link->ifname().c_str(), path->localSocket()); negotiatedPath = path; } if (remoteUtility < _localUtility) { - fprintf(stderr, "peer suggests path, its utility (%d) is less than ours (%d), we will NOT switch to %s on %s (ls=%llx)\n", remoteUtility, _localUtility, pathStr, link->ifname().c_str(), path->localSocket()); + //fprintf(stderr, "peer suggests path, its utility (%d) is less than ours (%d), we will NOT switch to %s on %s (ls=%llx)\n", remoteUtility, _localUtility, pathStr, link->ifname().c_str(), path->localSocket()); } if (remoteUtility == _localUtility) { - fprintf(stderr, "peer suggest path, but utility is equal, picking choice made by peer with greater identity.\n"); + //fprintf(stderr, "peer suggest path, but utility is equal, picking choice made by peer with greater identity.\n"); if (_peer->_id.address().toInt() > RR->node->identity().address().toInt()) { - fprintf(stderr, "peer identity was greater, going with their choice of %s on %s (ls=%llx)\n", pathStr, link->ifname().c_str(), path->localSocket()); + //fprintf(stderr, "peer identity was greater, going with their choice of %s on %s (ls=%llx)\n", pathStr, link->ifname().c_str(), path->localSocket()); negotiatedPath = path; } else { - fprintf(stderr, "our identity was greater, no change\n"); + //fprintf(stderr, "our identity was greater, no change\n"); } } } @@ -522,18 +522,18 @@ void Bond::pathNegotiationCheck(void *tPtr, const int64_t now) _localUtility -= ZT_MULTIPATH_FAILOVER_HANDICAP_NEGOTIATED; } if ((now - _lastSentPathNegotiationRequest) > ZT_PATH_NEGOTIATION_CUTOFF_TIME) { - fprintf(stderr, "BT: (sync) it's been long enough, sending more requests.\n"); + //fprintf(stderr, "BT: (sync) it's been long enough, sending more requests.\n"); _numSentPathNegotiationRequests = 0; } if (_numSentPathNegotiationRequests < ZT_PATH_NEGOTIATION_TRY_COUNT) { if (_localUtility >= 0) { - fprintf(stderr, "BT: (sync) paths appear to be out of sync (utility=%d)\n", _localUtility); + //fprintf(stderr, "BT: (sync) paths appear to be out of sync (utility=%d)\n", _localUtility); sendPATH_NEGOTIATION_REQUEST(tPtr, _paths[maxOutPathIdx]); ++_numSentPathNegotiationRequests; _lastSentPathNegotiationRequest = now; _paths[maxOutPathIdx]->address().toString(pathStr); SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[maxOutPathIdx]->localSocket()); - fprintf(stderr, "sending request to use %s on %s, ls=%llx, utility=%d\n", pathStr, link->ifname().c_str(), _paths[maxOutPathIdx]->localSocket(), _localUtility); + //fprintf(stderr, "sending request to use %s on %s, ls=%llx, utility=%d\n", pathStr, link->ifname().c_str(), _paths[maxOutPathIdx]->localSocket(), _localUtility); } } /** @@ -542,7 +542,7 @@ void Bond::pathNegotiationCheck(void *tPtr, const int64_t now) else if ((now - _lastSentPathNegotiationRequest) > (2 * ZT_PATH_NEGOTIATION_CHECK_INTERVAL)) { if (_localUtility == 0) { // There's no loss to us, just switch without sending a another request - fprintf(stderr, "BT: (sync) giving up, switching to remote peer's path.\n"); + //fprintf(stderr, "BT: (sync) giving up, switching to remote peer's path.\n"); negotiatedPath = _paths[maxInPathIdx]; } } @@ -551,7 +551,7 @@ void Bond::pathNegotiationCheck(void *tPtr, const int64_t now) void Bond::sendPATH_NEGOTIATION_REQUEST(void *tPtr, const SharedPtr &path) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "sendPATH_NEGOTIATION_REQUEST() %s %s\n", getLink(path)->ifname().c_str(), pathStr); + //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "sendPATH_NEGOTIATION_REQUEST() %s %s\n", getLink(path)->ifname().c_str(), pathStr); if (_abLinkSelectMethod != ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { return; } @@ -566,7 +566,7 @@ void Bond::sendPATH_NEGOTIATION_REQUEST(void *tPtr, const SharedPtr &path) void Bond::sendACK(void *tPtr,const SharedPtr &path,const int64_t localSocket, const InetAddress &atAddress,int64_t now) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "sendACK() %s %s\n", getLink(path)->ifname().c_str(), pathStr); + //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "sendACK() %s %s\n", getLink(path)->ifname().c_str(), pathStr); Packet outp(_peer->_id.address(),RR->identity.address(),Packet::VERB_ACK); int32_t bytesToAck = 0; std::map::iterator it = path->ackStatsIn.begin(); @@ -589,7 +589,7 @@ void Bond::sendACK(void *tPtr,const SharedPtr &path,const int64_t localSoc void Bond::sendQOS_MEASUREMENT(void *tPtr,const SharedPtr &path,const int64_t localSocket, const InetAddress &atAddress,int64_t now) { - //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "sendQOS() %s %s\n", getLink(path)->ifname().c_str(), pathStr); + //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "sendQOS() %s %s\n", getLink(path)->ifname().c_str(), pathStr); const int64_t _now = RR->node->now(); Packet outp(_peer->_id.address(),RR->identity.address(),Packet::VERB_QOS_MEASUREMENT); char qosData[ZT_QOS_MAX_PACKET_SIZE]; @@ -969,24 +969,24 @@ void Bond::estimatePathQuality(const int64_t now) return; } if (!_header) { - fprintf(stdout, "now, bonded, relativeUnderload, flows, "); + //fprintf(stdout, "now, bonded, relativeUnderload, flows, "); for(unsigned int i=0;iaddress().toString(pathStr); std::string label = std::string((pathStr)) + " " + getLink(_paths[i])->ifname(); for (int i=0; i<19; ++i) { - fprintf(stdout, "%s, ", label.c_str()); + //fprintf(stdout, "%s, ", label.c_str()); } } } _header=true; } /* - fprintf(stdout, "%ld, %d, %d, %d, ",((now - RR->bc->getBondStartTime())),_numBondedPaths,_totalBondUnderload, _flows.size()); + //fprintf(stdout, "%ld, %d, %d, %d, ",((now - RR->bc->getBondStartTime())),_numBondedPaths,_totalBondUnderload, _flows.size()); for(unsigned int i=0;iaddress().toString(pathStr); - fprintf(stdout, "%s, %s, %8.3f, %8.3f, %8.3f, %5.3f, %5.3f, %5.3f, %8f, %5.3f, %5.3f, %d, %5.3f, %d, %d, %d, %d, %d, %d, ", + //fprintf(stdout, "%s, %s, %8.3f, %8.3f, %8.3f, %5.3f, %5.3f, %5.3f, %8f, %5.3f, %5.3f, %d, %5.3f, %d, %d, %d, %d, %d, %d, ", getLink(_paths[i])->ifname().c_str(), pathStr, _paths[i]->_latencyMean, lat[i],pdv[i], _paths[i]->_packetLossRatio, plr[i],per[i],thr[i],thm[i],thv[i],(now - _paths[i]->lastIn()),quality[i],alloc[i], _paths[i]->_relativeByteLoad, _paths[i]->_assignedFlowCount, _paths[i]->alive(now, true), _paths[i]->eligible(now,_ackSendInterval), _paths[i]->qosStatsOut.size()); } @@ -1040,7 +1040,7 @@ void Bond::processBalanceTasks(const int64_t now) } if (!_paths[i]->eligible(now,_ackSendInterval) && _paths[i]->_shouldReallocateFlows) { _paths[i]->address().toString(curPathStr); - fprintf(stderr, "%d reallocating flows from dead path %s on %s\n", (RR->node->now() - RR->bc->getBondStartTime()), curPathStr, getLink(_paths[i])->ifname().c_str()); + //fprintf(stderr, "%d reallocating flows from dead path %s on %s\n", (RR->node->now() - RR->bc->getBondStartTime()), curPathStr, getLink(_paths[i])->ifname().c_str()); std::map >::iterator flow_it = _flows.begin(); while (flow_it != _flows.end()) { if (flow_it->second->assignedPath() == _paths[i]) { @@ -1066,7 +1066,7 @@ void Bond::processBalanceTasks(const int64_t now) } if (_paths[i] && _paths[i]->bonded() && _paths[i]->eligible(now,_ackSendInterval) && (_paths[i]->_allocation < minimumAllocationValue) && _paths[i]->_assignedFlowCount) { _paths[i]->address().toString(curPathStr); - fprintf(stderr, "%d reallocating flows from under-performing path %s on %s\n", (RR->node->now() - RR->bc->getBondStartTime()), curPathStr, getLink(_paths[i])->ifname().c_str()); + //fprintf(stderr, "%d reallocating flows from under-performing path %s on %s\n", (RR->node->now() - RR->bc->getBondStartTime()), curPathStr, getLink(_paths[i])->ifname().c_str()); std::map >::iterator flow_it = _flows.begin(); while (flow_it != _flows.end()) { if (flow_it->second->assignedPath() == _paths[i]) { @@ -1119,7 +1119,7 @@ void Bond::processBalanceTasks(const int64_t now) while (flow_it != _flows.end()) { if (flow_it->second->_previouslyAssignedPath && flow_it->second->_previouslyAssignedPath->eligible(now, _ackSendInterval) && (flow_it->second->_previouslyAssignedPath->_allocation >= (minimumAllocationValue * 2))) { - fprintf(stderr, "moving flow back onto its previous path assignment (based on eligibility)\n"); + //fprintf(stderr, "moving flow back onto its previous path assignment (based on eligibility)\n"); (flow_it->second->_assignedPath->_assignedFlowCount)--; flow_it->second->assignPath(flow_it->second->_previouslyAssignedPath,now); (flow_it->second->_previouslyAssignedPath->_assignedFlowCount)++; @@ -1136,7 +1136,7 @@ void Bond::processBalanceTasks(const int64_t now) while (flow_it != _flows.end()) { if (flow_it->second->_previouslyAssignedPath && flow_it->second->_previouslyAssignedPath->eligible(now, _ackSendInterval) && (flow_it->second->_previouslyAssignedPath->_allocation >= (minimumAllocationValue * 2))) { - fprintf(stderr, "moving flow back onto its previous path assignment (based on performance)\n"); + //fprintf(stderr, "moving flow back onto its previous path assignment (based on performance)\n"); (flow_it->second->_assignedPath->_assignedFlowCount)--; flow_it->second->assignPath(flow_it->second->_previouslyAssignedPath,now); (flow_it->second->_previouslyAssignedPath->_assignedFlowCount)++; @@ -1182,7 +1182,7 @@ void Bond::processActiveBackupTasks(const int64_t now) * Select initial "active" active-backup link */ if (!_abPath) { - fprintf(stderr, "%llu no active backup path yet...\n", ((now - RR->bc->getBondStartTime()))); + //fprintf(stderr, "%llu no active backup path yet...\n", ((now - RR->bc->getBondStartTime()))); /** * [Automatic mode] * The user has not explicitly specified links or their failover schedule, @@ -1192,13 +1192,13 @@ void Bond::processActiveBackupTasks(const int64_t now) * simply find the next eligible path. */ if (!userHasSpecifiedLinks()) { - fprintf(stderr, "%llu AB: (auto) user did not specify any links. waiting until we know more\n", ((now - RR->bc->getBondStartTime()))); + //fprintf(stderr, "%llu AB: (auto) user did not specify any links. waiting until we know more\n", ((now - RR->bc->getBondStartTime()))); for (int i=0; ieligible(now,_ackSendInterval)) { _paths[i]->address().toString(curPathStr); SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); if (link) { - fprintf(stderr, "%llu AB: (initial) [%d] found eligible path %s on: %s\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, link->ifname().c_str()); + //fprintf(stderr, "%llu AB: (initial) [%d] found eligible path %s on: %s\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, link->ifname().c_str()); } _abPath = _paths[i]; break; @@ -1210,9 +1210,9 @@ void Bond::processActiveBackupTasks(const int64_t now) * The user has specified links or failover rules that the bonding policy should adhere to. */ else if (userHasSpecifiedLinks()) { - fprintf(stderr, "%llu AB: (manual) no active backup link, checking local.conf\n", ((now - RR->bc->getBondStartTime()))); + //fprintf(stderr, "%llu AB: (manual) no active backup link, checking local.conf\n", ((now - RR->bc->getBondStartTime()))); if (userHasSpecifiedPrimaryLink()) { - fprintf(stderr, "%llu AB: (manual) user has specified primary link, looking for it.\n", ((now - RR->bc->getBondStartTime()))); + //fprintf(stderr, "%llu AB: (manual) user has specified primary link, looking for it.\n", ((now - RR->bc->getBondStartTime()))); for (int i=0; ieligible(now,_ackSendInterval) && link->primary()) { if (!_paths[i]->preferred()) { _paths[i]->address().toString(curPathStr); - fprintf(stderr, "%llu AB: (initial) [%d] found path on primary link, taking note in case we don't find a preferred path\n", ((now - RR->bc->getBondStartTime())), i); + //fprintf(stderr, "%llu AB: (initial) [%d] found path on primary link, taking note in case we don't find a preferred path\n", ((now - RR->bc->getBondStartTime())), i); nonPreferredPath = _paths[i]; bFoundPrimaryLink = true; } @@ -1230,7 +1230,7 @@ void Bond::processActiveBackupTasks(const int64_t now) _abPath->address().toString(curPathStr); SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); if (link) { - fprintf(stderr, "%llu AB: (initial) [%d] found preferred path %s on primary link: %s\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, link->ifname().c_str()); + //fprintf(stderr, "%llu AB: (initial) [%d] found preferred path %s on primary link: %s\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, link->ifname().c_str()); } bFoundPrimaryLink = true; break; @@ -1241,23 +1241,23 @@ void Bond::processActiveBackupTasks(const int64_t now) _abPath->address().toString(curPathStr); SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _abPath->localSocket()); if (link) { - fprintf(stderr, "%llu AB: (initial) found preferred primary path: %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, link->ifname().c_str()); + //fprintf(stderr, "%llu AB: (initial) found preferred primary path: %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, link->ifname().c_str()); } } else { if (bFoundPrimaryLink && nonPreferredPath) { - fprintf(stderr, "%llu AB: (initial) found a non-preferred primary path\n", ((now - RR->bc->getBondStartTime()))); + //fprintf(stderr, "%llu AB: (initial) found a non-preferred primary path\n", ((now - RR->bc->getBondStartTime()))); _abPath = nonPreferredPath; } } if (!_abPath) { - fprintf(stderr, "%llu AB: (initial) designated primary link is not yet ready\n", ((now - RR->bc->getBondStartTime()))); + //fprintf(stderr, "%llu AB: (initial) designated primary link is not yet ready\n", ((now - RR->bc->getBondStartTime()))); // TODO: Should fail-over to specified backup or just wait? } } else if (!userHasSpecifiedPrimaryLink()) { int _abIdx = ZT_MAX_PEER_NETWORK_PATHS; - fprintf(stderr, "%llu AB: (initial) user did not specify primary link, just picking something\n", ((now - RR->bc->getBondStartTime()))); + //fprintf(stderr, "%llu AB: (initial) user did not specify primary link, just picking something\n", ((now - RR->bc->getBondStartTime()))); for (int i=0; ieligible(now,_ackSendInterval)) { _abIdx = i; @@ -1265,13 +1265,13 @@ void Bond::processActiveBackupTasks(const int64_t now) } } if (_abIdx == ZT_MAX_PEER_NETWORK_PATHS) { - fprintf(stderr, "%llu AB: (initial) unable to find a candidate next-best, no change\n", ((now - RR->bc->getBondStartTime()))); + //fprintf(stderr, "%llu AB: (initial) unable to find a candidate next-best, no change\n", ((now - RR->bc->getBondStartTime()))); } else { _abPath = _paths[_abIdx]; SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _abPath->localSocket()); if (link) { - fprintf(stderr, "%llu AB: (initial) selected non-primary link idx=%d, %s on %s\n", ((now - RR->bc->getBondStartTime())), _abIdx, pathStr, link->ifname().c_str()); + //fprintf(stderr, "%llu AB: (initial) selected non-primary link idx=%d, %s on %s\n", ((now - RR->bc->getBondStartTime())), _abIdx, pathStr, link->ifname().c_str()); } } } @@ -1288,7 +1288,7 @@ void Bond::processActiveBackupTasks(const int64_t now) (*it)->address().toString(curPathStr); SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, (*it)->localSocket()); if (link) { - fprintf(stderr, "%llu AB: (fq) %s on %s is now ineligible, removing from failover queue\n", ((now - RR->bc->getBondStartTime())), curPathStr, link->ifname().c_str()); + //fprintf(stderr, "%llu AB: (fq) %s on %s is now ineligible, removing from failover queue\n", ((now - RR->bc->getBondStartTime())), curPathStr, link->ifname().c_str()); } it = _abFailoverQueue.erase(it); } else { @@ -1360,7 +1360,7 @@ void Bond::processActiveBackupTasks(const int64_t now) } if (!bFoundPathInQueue) { _paths[i]->address().toString(curPathStr); - fprintf(stderr, "%llu AB: (fq) [%d] added %s on %s to queue\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, getLink(_paths[i])->ifname().c_str()); + //fprintf(stderr, "%llu AB: (fq) [%d] added %s on %s to queue\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, getLink(_paths[i])->ifname().c_str()); _abFailoverQueue.push_front(_paths[i]); } } @@ -1405,7 +1405,7 @@ void Bond::processActiveBackupTasks(const int64_t now) } if (!bFoundPathInQueue) { _paths[i]->address().toString(curPathStr); - fprintf(stderr, "%llu AB: (fq) [%d] added %s on %s to queue\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, getLink(_paths[i])->ifname().c_str()); + //fprintf(stderr, "%llu AB: (fq) [%d] added %s on %s to queue\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, getLink(_paths[i])->ifname().c_str()); _abFailoverQueue.push_front(_paths[i]); } } @@ -1413,7 +1413,7 @@ void Bond::processActiveBackupTasks(const int64_t now) } _abFailoverQueue.sort(PathQualityComparator()); if (_abFailoverQueue.empty()) { - fprintf(stderr, "%llu AB: (fq) the failover queue is empty, the active-backup bond is no longer fault-tolerant\n", ((now - RR->bc->getBondStartTime()))); + //fprintf(stderr, "%llu AB: (fq) the failover queue is empty, the active-backup bond is no longer fault-tolerant\n", ((now - RR->bc->getBondStartTime()))); } } /** @@ -1426,13 +1426,13 @@ void Bond::processActiveBackupTasks(const int64_t now) * Fulfill primary reselect obligations */ if (_abPath && !_abPath->eligible(now,_ackSendInterval)) { // Implicit ZT_MULTIPATH_RESELECTION_POLICY_FAILURE - _abPath->address().toString(curPathStr); fprintf(stderr, "%llu AB: (failure) failover event!, active backup path (%s) is no-longer eligible\n", ((now - RR->bc->getBondStartTime())), curPathStr); + _abPath->address().toString(curPathStr); //fprintf(stderr, "%llu AB: (failure) failover event!, active backup path (%s) is no-longer eligible\n", ((now - RR->bc->getBondStartTime())), curPathStr); if (!_abFailoverQueue.empty()) { - fprintf(stderr, "%llu AB: (failure) there are (%lu) links in queue to choose from...\n", ((now - RR->bc->getBondStartTime())), _abFailoverQueue.size()); + //fprintf(stderr, "%llu AB: (failure) there are (%lu) links in queue to choose from...\n", ((now - RR->bc->getBondStartTime())), _abFailoverQueue.size()); dequeueNextActiveBackupPath(now); - _abPath->address().toString(curPathStr); fprintf(stderr, "%llu AB: (failure) switched to %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, getLink(_abPath)->ifname().c_str()); + _abPath->address().toString(curPathStr); //fprintf(stderr, "%llu AB: (failure) switched to %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, getLink(_abPath)->ifname().c_str()); } else { - fprintf(stderr, "%llu AB: (failure) nothing available in the link queue, doing nothing.\n", ((now - RR->bc->getBondStartTime()))); + //fprintf(stderr, "%llu AB: (failure) nothing available in the link queue, doing nothing.\n", ((now - RR->bc->getBondStartTime()))); } } /** @@ -1444,17 +1444,17 @@ void Bond::processActiveBackupTasks(const int64_t now) if (_abLinkSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_ALWAYS) { if (_abPath && !getLink(_abPath)->primary() && getLink(_abFailoverQueue.front())->primary()) { - fprintf(stderr, "%llu AB: (always) switching to available primary\n", ((now - RR->bc->getBondStartTime()))); + //fprintf(stderr, "%llu AB: (always) switching to available primary\n", ((now - RR->bc->getBondStartTime()))); dequeueNextActiveBackupPath(now); } } if (_abLinkSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_BETTER) { if (_abPath && !getLink(_abPath)->primary()) { - fprintf(stderr, "%llu AB: (better) active backup has switched to \"better\" primary link according to re-select policy.\n", ((now - RR->bc->getBondStartTime()))); + //fprintf(stderr, "%llu AB: (better) active backup has switched to \"better\" primary link according to re-select policy.\n", ((now - RR->bc->getBondStartTime()))); if (getLink(_abFailoverQueue.front())->primary() && (_abFailoverQueue.front()->_failoverScore > _abPath->_failoverScore)) { dequeueNextActiveBackupPath(now); - fprintf(stderr, "%llu AB: (better) switched back to user-defined primary\n", ((now - RR->bc->getBondStartTime()))); + //fprintf(stderr, "%llu AB: (better) switched back to user-defined primary\n", ((now - RR->bc->getBondStartTime()))); } } } @@ -1465,7 +1465,7 @@ void Bond::processActiveBackupTasks(const int64_t now) if (_abFailoverQueue.front()->_negotiated) { dequeueNextActiveBackupPath(now); _abPath->address().toString(prevPathStr); - fprintf(stderr, "%llu AB: (optimize) switched to negotiated path %s on %s\n", ((now - RR->bc->getBondStartTime())), prevPathStr, getLink(_abPath)->ifname().c_str()); + //fprintf(stderr, "%llu AB: (optimize) switched to negotiated path %s on %s\n", ((now - RR->bc->getBondStartTime())), prevPathStr, getLink(_abPath)->ifname().c_str()); _lastPathNegotiationCheck = now; } else { @@ -1483,7 +1483,7 @@ void Bond::processActiveBackupTasks(const int64_t now) _abPath->address().toString(prevPathStr); dequeueNextActiveBackupPath(now); _abPath->address().toString(curPathStr); - fprintf(stderr, "%llu AB: (optimize) switched from %s on %s (fs=%d) to %s on %s (fs=%d)\n", ((now - RR->bc->getBondStartTime())), prevPathStr, getLink(oldPath)->ifname().c_str(), prevFScore, curPathStr, getLink(_abPath)->ifname().c_str(), newFScore); + //fprintf(stderr, "%llu AB: (optimize) switched from %s on %s (fs=%d) to %s on %s (fs=%d)\n", ((now - RR->bc->getBondStartTime())), prevPathStr, getLink(oldPath)->ifname().c_str(), prevFScore, curPathStr, getLink(_abPath)->ifname().c_str(), newFScore); } } } @@ -1640,7 +1640,7 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _downDelay = templateBond->_downDelay; _upDelay = templateBond->_upDelay; - fprintf(stderr, "TIMERS: strat=%d, fi= %d, bmi= %d, qos= %d, ack= %d, estimateInt= %d, refractory= %d, ud= %d, dd= %d\n", + /*fprintf(stderr, "TIMERS: strat=%d, fi= %d, bmi= %d, qos= %d, ack= %d, estimateInt= %d, refractory= %d, ud= %d, dd= %d\n", _linkMonitorStrategy, _failoverInterval, _bondMonitorInterval, @@ -1650,10 +1650,11 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _defaultPathRefractoryPeriod, _upDelay, _downDelay); + */ if (templateBond->_linkMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_PASSIVE && templateBond->_failoverInterval != 0) { - fprintf(stderr, "warning: passive path monitoring was specified, this will prevent failovers from happening in a timely manner.\n"); + //fprintf(stderr, "warning: passive path monitoring was specified, this will prevent failovers from happening in a timely manner.\n"); } _abLinkSelectMethod = templateBond->_abLinkSelectMethod; memcpy(_qualityWeights, templateBond->_qualityWeights, ZT_QOS_WEIGHT_SIZE * sizeof(float)); @@ -1672,7 +1673,7 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _failoverInterval = originalBond._failoverInterval; } else { - fprintf(stderr, "warning: _failoverInterval (%d) is out of range, using default (%d)\n", originalBond._failoverInterval, _failoverInterval); + //fprintf(stderr, "warning: _failoverInterval (%d) is out of range, using default (%d)\n", originalBond._failoverInterval, _failoverInterval); } */ @@ -1726,17 +1727,17 @@ void Bond::dumpInfo(const int64_t now) return; } /* - fprintf(stderr, "---[ bp=%d, id=%llx, dd=%d, up=%d, pmi=%d, specifiedLinks=%d, _specifiedPrimaryLink=%d, _specifiedFailInst=%d ]\n", + //fprintf(stderr, "---[ bp=%d, id=%llx, dd=%d, up=%d, pmi=%d, specifiedLinks=%d, _specifiedPrimaryLink=%d, _specifiedFailInst=%d ]\n", _policy, _peer->identity().address().toInt(), _downDelay, _upDelay, _monitorInterval, _userHasSpecifiedLinks, _userHasSpecifiedPrimaryLink, _userHasSpecifiedFailoverInstructions); if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { - fprintf(stderr, "Paths (bp=%d, stats=%d, primaryReselect=%d) :\n", + //fprintf(stderr, "Paths (bp=%d, stats=%d, primaryReselect=%d) :\n", _policy, _shouldCollectPathStatistics, _abLinkSelectMethod); } if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { - fprintf(stderr, "Paths (bp=%d, stats=%d, fh=%d) :\n", + //fprintf(stderr, "Paths (bp=%d, stats=%d, fh=%d) :\n", _policy, _shouldCollectPathStatistics, _allowFlowHashing); }*/ if ((now - _lastPrintTS) < 2000) { @@ -1744,13 +1745,13 @@ void Bond::dumpInfo(const int64_t now) } _lastPrintTS = now; - fprintf(stderr, "\n\n"); + //fprintf(stderr, "\n\n"); for(int i=0; i link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); _paths[i]->address().toString(pathStr); - fprintf(stderr, " %2d: lat=%8.3f, ac=%3d, fail%5s, fscore=%6d, in=%7d, out=%7d, age=%7ld, ack=%7ld, ref=%6d, ls=%llx", + /*fprintf(stderr, " %2d: lat=%8.3f, ac=%3d, fail%5s, fscore=%6d, in=%7d, out=%7d, age=%7ld, ack=%7ld, ref=%6d, ls=%llx", i, _paths[i]->_latencyMean, _paths[i]->_allocation, @@ -1763,73 +1764,75 @@ void Bond::dumpInfo(const int64_t now) _paths[i]->_refractoryPeriod, _paths[i]->localSocket() ); + */ if (link->spare()) { - fprintf(stderr, " SPR."); + //fprintf(stderr, " SPR."); } else { - fprintf(stderr, " "); + //fprintf(stderr, " "); } if (link->primary()) { - fprintf(stderr, " PRIM."); + //fprintf(stderr, " PRIM."); } else { - fprintf(stderr, " "); + //fprintf(stderr, " "); } if (_paths[i]->allowed()) { - fprintf(stderr, " ALL."); + //fprintf(stderr, " ALL."); } else { - fprintf(stderr, " "); + //fprintf(stderr, " "); } if (_paths[i]->eligible(now,_ackSendInterval)) { - fprintf(stderr, " ELI."); + //fprintf(stderr, " ELI."); } else { - fprintf(stderr, " "); + //fprintf(stderr, " "); } if (_paths[i]->preferred()) { - fprintf(stderr, " PREF."); + //fprintf(stderr, " PREF."); } else { - fprintf(stderr, " "); + //fprintf(stderr, " "); } if (_paths[i]->_negotiated) { - fprintf(stderr, " NEG."); + //fprintf(stderr, " NEG."); } else { - fprintf(stderr, " "); + //fprintf(stderr, " "); } if (_paths[i]->bonded()) { - fprintf(stderr, " BOND "); + //fprintf(stderr, " BOND "); } else { - fprintf(stderr, " "); + //fprintf(stderr, " "); } if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP && _abPath && (_abPath == _paths[i].ptr())) { - fprintf(stderr, " ACTIVE "); + //fprintf(stderr, " ACTIVE "); } else if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { - fprintf(stderr, " "); + //fprintf(stderr, " "); } if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP && _abFailoverQueue.size() && (_abFailoverQueue.front().ptr() == _paths[i].ptr())) { - fprintf(stderr, " NEXT "); + //fprintf(stderr, " NEXT "); } else if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { - fprintf(stderr, " "); + //fprintf(stderr, " "); } - fprintf(stderr, "%5s %s\n", link->ifname().c_str(), pathStr); + //fprintf(stderr, "%5s %s\n", link->ifname().c_str(), pathStr); } } if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { if (!_abFailoverQueue.empty()) { - fprintf(stderr, "\nFailover Queue:\n"); + //fprintf(stderr, "\nFailover Queue:\n"); for (std::list >::iterator it(_abFailoverQueue.begin()); it!=_abFailoverQueue.end();++it) { (*it)->address().toString(currPathStr); SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, (*it)->localSocket()); - fprintf(stderr, "\t%8s\tspeed=%7d\trelSpeed=%3d\tipvPref=%3d\tfscore=%9d\t\t%s\n", + /*fprintf(stderr, "\t%8s\tspeed=%7d\trelSpeed=%3d\tipvPref=%3d\tfscore=%9d\t\t%s\n", link->ifname().c_str(), link->speed(), link->relativeSpeed(), link->ipvPref(), (*it)->_failoverScore, currPathStr); + */ } } else { - fprintf(stderr, "\nFailover Queue size = %lu\n", _abFailoverQueue.size()); + //fprintf(stderr, "\nFailover Queue size = %lu\n", _abFailoverQueue.size()); } } @@ -1837,20 +1840,21 @@ void Bond::dumpInfo(const int64_t now) || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { if (_numBondedPaths) { - fprintf(stderr, "\nBonded Paths:\n"); + //fprintf(stderr, "\nBonded Paths:\n"); for (int i=0; i<_numBondedPaths; ++i) { _paths[_bondedIdx[i]]->address().toString(currPathStr); SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[_bondedIdx[i]]->localSocket()); - fprintf(stderr, " [%d]\t%8s\tflows=%3d\tspeed=%7d\trelSpeed=%3d\tipvPref=%3d\tfscore=%9d\t\t%s\n", i, - //fprintf(stderr, " [%d]\t%8s\tspeed=%7d\trelSpeed=%3d\tflowCount=%2d\tipvPref=%3d\tfscore=%9d\t\t%s\n", i, + //fprintf(stderr, " [%d]\t%8s\tflows=%3d\tspeed=%7d\trelSpeed=%3d\tipvPref=%3d\tfscore=%9d\t\t%s\n", i, + /*fprintf(stderr, " [%d]\t%8s\tspeed=%7d\trelSpeed=%3d\tflowCount=%2d\tipvPref=%3d\tfscore=%9d\t\t%s\n", i, link->ifname().c_str(), _paths[_bondedIdx[i]]->_assignedFlowCount, link->speed(), link->relativeSpeed(), - //_paths[_bondedIdx[i]].p->assignedFlows.size(), + _paths[_bondedIdx[i]].p->assignedFlows.size(), link->ipvPref(), _paths[_bondedIdx[i]]->_failoverScore, currPathStr); + */ } } } diff --git a/node/BondController.cpp b/node/BondController.cpp index f7159dbc3..357daef7e 100644 --- a/node/BondController.cpp +++ b/node/BondController.cpp @@ -52,7 +52,7 @@ void BondController::addCustomLink(std::string& policyAlias, SharedPtr lin link->setAsUserSpecified(true); _interfaceToLinkMap[policyAlias].insert(std::pair>(link->ifname(), link)); } else { - fprintf(stderr, "link already exists=%s\n", link->ifname().c_str()); + //fprintf(stderr, "link already exists=%s\n", link->ifname().c_str()); // Link is already defined, overlay user settings } } @@ -79,27 +79,27 @@ bool BondController::assignBondingPolicyToPeer(int64_t identity, const std::stri SharedPtr BondController::createTransportTriggeredBond(const RuntimeEnvironment *renv, const SharedPtr& peer) { - fprintf(stderr, "createTransportTriggeredBond\n"); + //fprintf(stderr, "createTransportTriggeredBond\n"); Mutex::Lock _l(_bonds_m); int64_t identity = peer->identity().address().toInt(); Bond *bond = nullptr; if (!_bonds.count(identity)) { std::string policyAlias; - fprintf(stderr, "new bond, registering for %llx\n", identity); + //fprintf(stderr, "new bond, registering for %llx\n", identity); if (!_policyTemplateAssignments.count(identity)) { if (_defaultBondingPolicy) { - fprintf(stderr, " no assignment, using default (%d)\n", _defaultBondingPolicy); + //fprintf(stderr, " no assignment, using default (%d)\n", _defaultBondingPolicy); bond = new Bond(renv, _defaultBondingPolicy, peer); } if (!_defaultBondingPolicy && _defaultBondingPolicyStr.length()) { - fprintf(stderr, " no assignment, using default custom (%s)\n", _defaultBondingPolicyStr.c_str()); + //fprintf(stderr, " no assignment, using default custom (%s)\n", _defaultBondingPolicyStr.c_str()); bond = new Bond(renv, _bondPolicyTemplates[_defaultBondingPolicyStr].ptr(), peer); } } else { - fprintf(stderr, " assignment found for %llx, using it as a template (%s)\n", identity,_policyTemplateAssignments[identity].c_str()); + //fprintf(stderr, " assignment found for %llx, using it as a template (%s)\n", identity,_policyTemplateAssignments[identity].c_str()); if (!_bondPolicyTemplates[_policyTemplateAssignments[identity]]) { - fprintf(stderr, "unable to locate template (%s), ignoring assignment for (%llx), using defaults\n", _policyTemplateAssignments[identity].c_str(), identity); + //fprintf(stderr, "unable to locate template (%s), ignoring assignment for (%llx), using defaults\n", _policyTemplateAssignments[identity].c_str(), identity); bond = new Bond(renv, _defaultBondingPolicy, peer); } else { @@ -108,7 +108,7 @@ SharedPtr BondController::createTransportTriggeredBond(const RuntimeEnviro } } else { - fprintf(stderr, "bond already exists for %llx.\n", identity); + //fprintf(stderr, "bond already exists for %llx.\n", identity); } if (bond) { _bonds[identity] = bond; diff --git a/service/OneService.cpp b/service/OneService.cpp index 7f73e903f..96c9babbd 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -1581,7 +1581,7 @@ public: // Custom Policies json &customBondingPolicies = settings["policies"]; for (json::iterator policyItr = customBondingPolicies.begin(); policyItr != customBondingPolicies.end();++policyItr) { - fprintf(stderr, "\n\n--- (%s)\n", policyItr.key().c_str()); + //fprintf(stderr, "\n\n--- (%s)\n", policyItr.key().c_str()); // Custom Policy std::string customPolicyStr(policyItr.key()); json &customPolicy = policyItr.value(); @@ -1635,7 +1635,7 @@ public: // Policy-Specific link set json &links = customPolicy["links"]; for (json::iterator linkItr = links.begin(); linkItr != links.end();++linkItr) { - fprintf(stderr, "\t--- link (%s)\n", linkItr.key().c_str()); + //fprintf(stderr, "\t--- link (%s)\n", linkItr.key().c_str()); std::string linkNameStr(linkItr.key()); json &link = linkItr.value(); From a1b2ff772a246a0367e1bb4439c6eb2827e960ab Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Thu, 23 Jul 2020 00:15:38 -0700 Subject: [PATCH 106/362] Add new replacement condition in peer path redundancy logic to fix duplicate paths --- node/Peer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/node/Peer.cpp b/node/Peer.cpp index f99396aaa..ad3d47106 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -139,6 +139,9 @@ void Peer::received( if (q > replacePathQuality) { replacePathQuality = q; replacePath = i; + if (!_paths[i].p->alive(now)) { + break; // Stop searching, we found an identical dead path, replace the object + } } } else { replacePath = i; From 29ebda62ef38c9239802e2a61daf53f52147047f Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Thu, 23 Jul 2020 00:32:39 -0700 Subject: [PATCH 107/362] Remove (some) debug functions and traces --- node/Bond.cpp | 146 +--------------------------------------- node/BondController.cpp | 1 - node/Packet.hpp | 2 +- 3 files changed, 3 insertions(+), 146 deletions(-) diff --git a/node/Bond.cpp b/node/Bond.cpp index e96355ec9..609d62bc6 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -1660,7 +1660,6 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool memcpy(_qualityWeights, templateBond->_qualityWeights, ZT_QOS_WEIGHT_SIZE * sizeof(float)); } - // // Second, apply user specified values (only if they make sense) @@ -1705,11 +1704,8 @@ void Bond::setUserQualityWeights(float weights[], int len) } } - bool Bond::relevant() { - return _peer->identity().address().toInt() == 0x16a03a3d03 - || _peer->identity().address().toInt() == 0x4410300d03 - || _peer->identity().address().toInt() == 0x795cbf86fa; + return false; } SharedPtr Bond::getLink(const SharedPtr& path) @@ -1719,145 +1715,7 @@ SharedPtr Bond::getLink(const SharedPtr& path) void Bond::dumpInfo(const int64_t now) { - char pathStr[128]; - //char oldPathStr[128]; - char currPathStr[128]; - - if (!relevant()) { - return; - } - /* - //fprintf(stderr, "---[ bp=%d, id=%llx, dd=%d, up=%d, pmi=%d, specifiedLinks=%d, _specifiedPrimaryLink=%d, _specifiedFailInst=%d ]\n", - _policy, _peer->identity().address().toInt(), _downDelay, _upDelay, _monitorInterval, _userHasSpecifiedLinks, _userHasSpecifiedPrimaryLink, _userHasSpecifiedFailoverInstructions); - - if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { - //fprintf(stderr, "Paths (bp=%d, stats=%d, primaryReselect=%d) :\n", - _policy, _shouldCollectPathStatistics, _abLinkSelectMethod); - } - if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR - || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR - || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { - //fprintf(stderr, "Paths (bp=%d, stats=%d, fh=%d) :\n", - _policy, _shouldCollectPathStatistics, _allowFlowHashing); - }*/ - if ((now - _lastPrintTS) < 2000) { - return; - } - _lastPrintTS = now; - - //fprintf(stderr, "\n\n"); - - for(int i=0; i link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); - _paths[i]->address().toString(pathStr); - /*fprintf(stderr, " %2d: lat=%8.3f, ac=%3d, fail%5s, fscore=%6d, in=%7d, out=%7d, age=%7ld, ack=%7ld, ref=%6d, ls=%llx", - i, - _paths[i]->_latencyMean, - _paths[i]->_allocation, - link->failoverToLink().c_str(), - _paths[i]->_failoverScore, - _paths[i]->_packetsIn, - _paths[i]->_packetsOut, - (long)_paths[i]->age(now), - (long)_paths[i]->ackAge(now), - _paths[i]->_refractoryPeriod, - _paths[i]->localSocket() - ); - */ - if (link->spare()) { - //fprintf(stderr, " SPR."); - } else { - //fprintf(stderr, " "); - } - if (link->primary()) { - //fprintf(stderr, " PRIM."); - } else { - //fprintf(stderr, " "); - } - if (_paths[i]->allowed()) { - //fprintf(stderr, " ALL."); - } else { - //fprintf(stderr, " "); - } - if (_paths[i]->eligible(now,_ackSendInterval)) { - //fprintf(stderr, " ELI."); - } else { - //fprintf(stderr, " "); - } - if (_paths[i]->preferred()) { - //fprintf(stderr, " PREF."); - } else { - //fprintf(stderr, " "); - } - if (_paths[i]->_negotiated) { - //fprintf(stderr, " NEG."); - } else { - //fprintf(stderr, " "); - } - if (_paths[i]->bonded()) { - //fprintf(stderr, " BOND "); - } else { - //fprintf(stderr, " "); - } - if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP && _abPath && (_abPath == _paths[i].ptr())) { - //fprintf(stderr, " ACTIVE "); - } else if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { - //fprintf(stderr, " "); - } - if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP && _abFailoverQueue.size() && (_abFailoverQueue.front().ptr() == _paths[i].ptr())) { - //fprintf(stderr, " NEXT "); - } else if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { - //fprintf(stderr, " "); - } - //fprintf(stderr, "%5s %s\n", link->ifname().c_str(), pathStr); - } - } - - if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { - if (!_abFailoverQueue.empty()) { - //fprintf(stderr, "\nFailover Queue:\n"); - for (std::list >::iterator it(_abFailoverQueue.begin()); it!=_abFailoverQueue.end();++it) { - (*it)->address().toString(currPathStr); - SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, (*it)->localSocket()); - /*fprintf(stderr, "\t%8s\tspeed=%7d\trelSpeed=%3d\tipvPref=%3d\tfscore=%9d\t\t%s\n", - link->ifname().c_str(), - link->speed(), - link->relativeSpeed(), - link->ipvPref(), - (*it)->_failoverScore, - currPathStr); - */ - } - } - else - { - //fprintf(stderr, "\nFailover Queue size = %lu\n", _abFailoverQueue.size()); - } - } - - if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR - || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR - || _bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { - if (_numBondedPaths) { - //fprintf(stderr, "\nBonded Paths:\n"); - for (int i=0; i<_numBondedPaths; ++i) { - _paths[_bondedIdx[i]]->address().toString(currPathStr); - SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[_bondedIdx[i]]->localSocket()); - //fprintf(stderr, " [%d]\t%8s\tflows=%3d\tspeed=%7d\trelSpeed=%3d\tipvPref=%3d\tfscore=%9d\t\t%s\n", i, - /*fprintf(stderr, " [%d]\t%8s\tspeed=%7d\trelSpeed=%3d\tflowCount=%2d\tipvPref=%3d\tfscore=%9d\t\t%s\n", i, - link->ifname().c_str(), - _paths[_bondedIdx[i]]->_assignedFlowCount, - link->speed(), - link->relativeSpeed(), - _paths[_bondedIdx[i]].p->assignedFlows.size(), - link->ipvPref(), - _paths[_bondedIdx[i]]->_failoverScore, - currPathStr); - */ - } - } - } + // Omitted } } // namespace ZeroTier \ No newline at end of file diff --git a/node/BondController.cpp b/node/BondController.cpp index 357daef7e..4fed2befd 100644 --- a/node/BondController.cpp +++ b/node/BondController.cpp @@ -79,7 +79,6 @@ bool BondController::assignBondingPolicyToPeer(int64_t identity, const std::stri SharedPtr BondController::createTransportTriggeredBond(const RuntimeEnvironment *renv, const SharedPtr& peer) { - //fprintf(stderr, "createTransportTriggeredBond\n"); Mutex::Lock _l(_bonds_m); int64_t identity = peer->identity().address().toInt(); Bond *bond = nullptr; diff --git a/node/Packet.hpp b/node/Packet.hpp index ca789db81..f1112403e 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -56,7 +56,7 @@ * + Inline push of CertificateOfMembership deprecated * 9 - 1.2.0 ... 1.2.14 * 10 - 1.4.0 ... CURRENT - * + Multipath capability and load balancing + * + Multipath capability and load balancing (tentative) */ #define ZT_PROTO_VERSION 10 From 251b06d812357ee9ea0bd9aa8b7eeb868dad6248 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 23 Jul 2020 09:38:50 -0700 Subject: [PATCH 108/362] revert redis for member status --- controller/PostgreSQL.cpp | 258 +++++++++++++++++++------------------- 1 file changed, 129 insertions(+), 129 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 0f5fde77b..d89b77814 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -273,18 +273,18 @@ void PostgreSQL::initializeNetworks(PGconn *conn) std::string setKey = "networks:{" + _myAddressStr + "}"; - if (_rc != NULL) { - try { - if (_rc->clusterMode) { - _cluster->del(setKey); - } else { - _redis->del(setKey); - } - } catch (sw::redis::Error &e) { - // del can throw an error if the key doesn't exist - // swallow it and move along - } - } + // if (_rc != NULL) { + // try { + // if (_rc->clusterMode) { + // _cluster->del(setKey); + // } else { + // _redis->del(setKey); + // } + // } catch (sw::redis::Error &e) { + // // del can throw an error if the key doesn't exist + // // swallow it and move along + // } + // } std::unordered_set networkSet; @@ -437,17 +437,17 @@ void PostgreSQL::initializeNetworks(PGconn *conn) PQclear(res); - if(!networkSet.empty()) { - if (_rc && _rc->clusterMode) { - auto tx = _cluster->transaction(_myAddressStr, true); - tx.sadd(setKey, networkSet.begin(), networkSet.end()); - tx.exec(); - } else if (_rc && !_rc->clusterMode) { - auto tx = _redis->transaction(true); - tx.sadd(setKey, networkSet.begin(), networkSet.end()); - tx.exec(); - } - } + // if(!networkSet.empty()) { + // if (_rc && _rc->clusterMode) { + // auto tx = _cluster->transaction(_myAddressStr, true); + // tx.sadd(setKey, networkSet.begin(), networkSet.end()); + // tx.exec(); + // } else if (_rc && !_rc->clusterMode) { + // auto tx = _redis->transaction(true); + // tx.sadd(setKey, networkSet.begin(), networkSet.end()); + // tx.exec(); + // } + // } if (++this->_ready == 2) { if (_waitNoticePrinted) { @@ -471,36 +471,36 @@ void PostgreSQL::initializeMembers(PGconn *conn) fprintf(stderr, "Bad Database Connection: %s", PQerrorMessage(conn)); exit(1); } - std::string setKeyBase = "network-nodes-all:{" + _myAddressStr + "}:"; + // std::string setKeyBase = "network-nodes-all:{" + _myAddressStr + "}:"; - if (_rc != NULL) { - std::lock_guard l(_networks_l); - std::unordered_set deletes; - for ( auto it : _networks) { - uint64_t nwid_i = it.first; - char nwidTmp[64] = {0}; - OSUtils::ztsnprintf(nwidTmp, sizeof(nwidTmp), "%.16llx", nwid_i); - std::string nwid(nwidTmp); - std::string key = setKeyBase + nwid; - deletes.insert(key); - } + // if (_rc != NULL) { + // std::lock_guard l(_networks_l); + // std::unordered_set deletes; + // for ( auto it : _networks) { + // uint64_t nwid_i = it.first; + // char nwidTmp[64] = {0}; + // OSUtils::ztsnprintf(nwidTmp, sizeof(nwidTmp), "%.16llx", nwid_i); + // std::string nwid(nwidTmp); + // std::string key = setKeyBase + nwid; + // deletes.insert(key); + // } - if (!deletes.empty()) { - if (_rc->clusterMode) { - auto tx = _cluster->transaction(_myAddressStr, true); - for (std::string k : deletes) { - tx.del(k); - } - tx.exec(); - } else { - auto tx = _redis->transaction(true); - for (std::string k : deletes) { - tx.del(k); - } - tx.exec(); - } - } - } + // if (!deletes.empty()) { + // if (_rc->clusterMode) { + // auto tx = _cluster->transaction(_myAddressStr, true); + // for (std::string k : deletes) { + // tx.del(k); + // } + // tx.exec(); + // } else { + // auto tx = _redis->transaction(true); + // for (std::string k : deletes) { + // tx.del(k); + // } + // tx.exec(); + // } + // } + // } const char *params[1] = { _myAddressStr.c_str() @@ -540,7 +540,7 @@ void PostgreSQL::initializeMembers(PGconn *conn) std::string memberId(PQgetvalue(res, i, 0)); std::string networkId(PQgetvalue(res, i, 1)); - networkMembers.insert(std::pair(setKeyBase+networkId, memberId)); + // networkMembers.insert(std::pair(setKeyBase+networkId, memberId)); std::string ctime = PQgetvalue(res, i, 5); config["id"] = memberId; @@ -647,23 +647,23 @@ void PostgreSQL::initializeMembers(PGconn *conn) PQclear(res); - if (!networkMembers.empty()) { - if (_rc != NULL) { - if (_rc->clusterMode) { - auto tx = _cluster->transaction(_myAddressStr, true); - for (auto it : networkMembers) { - tx.sadd(it.first, it.second); - } - tx.exec(); - } else { - auto tx = _redis->transaction(true); - for (auto it : networkMembers) { - tx.sadd(it.first, it.second); - } - tx.exec(); - } - } - } + // if (!networkMembers.empty()) { + // if (_rc != NULL) { + // if (_rc->clusterMode) { + // auto tx = _cluster->transaction(_myAddressStr, true); + // for (auto it : networkMembers) { + // tx.sadd(it.first, it.second); + // } + // tx.exec(); + // } else { + // auto tx = _redis->transaction(true); + // for (auto it : networkMembers) { + // tx.sadd(it.first, it.second); + // } + // tx.exec(); + // } + // } + // } if (++this->_ready == 2) { if (_waitNoticePrinted) { fprintf(stderr,"[%s] NOTICE: %.10llx controller PostgreSQL data download complete." ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt()); @@ -717,7 +717,7 @@ void PostgreSQL::heartbeat() std::string build = std::to_string(ZEROTIER_ONE_VERSION_BUILD); std::string now = std::to_string(ts); std::string host_port = std::to_string(_listenPort); - std::string use_redis = (_rc != NULL) ? "true" : "false"; + std::string use_redis = "false"; // (_rc != NULL) ? "true" : "false"; const char *values[10] = { controllerId, hostname, @@ -750,13 +750,13 @@ void PostgreSQL::heartbeat() } PQclear(res); } - if (_rc != NULL) { - if (_rc->clusterMode) { - _cluster->zadd("controllers", controllerId, ts); - } else { - _redis->zadd("controllers", controllerId, ts); - } - } + // if (_rc != NULL) { + // if (_rc->clusterMode) { + // _cluster->zadd("controllers", controllerId, ts); + // } else { + // _redis->zadd("controllers", controllerId, ts); + // } + // } std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } @@ -1445,20 +1445,20 @@ void PostgreSQL::commitThread() } catch (std::exception &e) { fprintf(stderr, "ERROR: Error updating member: %s\n", e.what()); } - if (_rc != NULL) { - try { - std::string id = (*config)["id"]; - std::string controllerId = _myAddressStr.c_str(); - std::string key = "networks:{" + controllerId + "}"; - if (_rc->clusterMode) { - _cluster->sadd(key, id); - } else { - _redis->sadd(key, id); - } - } catch (sw::redis::Error &e) { - fprintf(stderr, "ERROR: Error adding network to Redis: %s\n", e.what()); - } - } + // if (_rc != NULL) { + // try { + // std::string id = (*config)["id"]; + // std::string controllerId = _myAddressStr.c_str(); + // std::string key = "networks:{" + controllerId + "}"; + // if (_rc->clusterMode) { + // _cluster->sadd(key, id); + // } else { + // _redis->sadd(key, id); + // } + // } catch (sw::redis::Error &e) { + // fprintf(stderr, "ERROR: Error adding network to Redis: %s\n", e.what()); + // } + // } } else if (objtype == "_delete_network") { try { std::string networkId = (*config)["nwid"]; @@ -1482,22 +1482,22 @@ void PostgreSQL::commitThread() } catch (std::exception &e) { fprintf(stderr, "ERROR: Error deleting network: %s\n", e.what()); } - if (_rc != NULL) { - try { - std::string id = (*config)["id"]; - std::string controllerId = _myAddressStr.c_str(); - std::string key = "networks:{" + controllerId + "}"; - if (_rc->clusterMode) { - _cluster->srem(key, id); - _cluster->del("network-nodes-online:{"+controllerId+"}:"+id); - } else { - _redis->srem(key, id); - _redis->del("network-nodes-online:{"+controllerId+"}:"+id); - } - } catch (sw::redis::Error &e) { - fprintf(stderr, "ERROR: Error adding network to Redis: %s\n", e.what()); - } - } + // if (_rc != NULL) { + // try { + // std::string id = (*config)["id"]; + // std::string controllerId = _myAddressStr.c_str(); + // std::string key = "networks:{" + controllerId + "}"; + // if (_rc->clusterMode) { + // _cluster->srem(key, id); + // _cluster->del("network-nodes-online:{"+controllerId+"}:"+id); + // } else { + // _redis->srem(key, id); + // _redis->del("network-nodes-online:{"+controllerId+"}:"+id); + // } + // } catch (sw::redis::Error &e) { + // fprintf(stderr, "ERROR: Error adding network to Redis: %s\n", e.what()); + // } + // } } else if (objtype == "_delete_member") { try { std::string memberId = (*config)["id"]; @@ -1525,23 +1525,23 @@ void PostgreSQL::commitThread() } catch (std::exception &e) { fprintf(stderr, "ERROR: Error deleting member: %s\n", e.what()); } - if (_rc != NULL) { - try { - std::string memberId = (*config)["id"]; - std::string networkId = (*config)["nwid"]; - std::string controllerId = _myAddressStr.c_str(); - std::string key = "network-nodes-all:{" + controllerId + "}:" + networkId; - if (_rc->clusterMode) { - _cluster->srem(key, memberId); - _cluster->del("member:{"+controllerId+"}:"+networkId+":"+memberId); - } else { - _redis->srem(key, memberId); - _redis->del("member:{"+controllerId+"}:"+networkId+":"+memberId); - } - } catch (sw::redis::Error &e) { - fprintf(stderr, "ERROR: Error deleting member from Redis: %s\n", e.what()); - } - } + // if (_rc != NULL) { + // try { + // std::string memberId = (*config)["id"]; + // std::string networkId = (*config)["nwid"]; + // std::string controllerId = _myAddressStr.c_str(); + // std::string key = "network-nodes-all:{" + controllerId + "}:" + networkId; + // if (_rc->clusterMode) { + // _cluster->srem(key, memberId); + // _cluster->del("member:{"+controllerId+"}:"+networkId+":"+memberId); + // } else { + // _redis->srem(key, memberId); + // _redis->del("member:{"+controllerId+"}:"+networkId+":"+memberId); + // } + // } catch (sw::redis::Error &e) { + // fprintf(stderr, "ERROR: Error deleting member from Redis: %s\n", e.what()); + // } + // } } else { fprintf(stderr, "ERROR: unknown objtype"); } @@ -1549,7 +1549,7 @@ void PostgreSQL::commitThread() fprintf(stderr, "ERROR: Error getting objtype: %s\n", e.what()); } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } PQfinish(conn); @@ -1564,11 +1564,11 @@ void PostgreSQL::onlineNotificationThread() { waitForReady(); - if (_rc != NULL) { - onlineNotification_Redis(); - } else { + // if (_rc != NULL) { + // onlineNotification_Redis(); + // } else { onlineNotification_Postgres(); - } + // } } void PostgreSQL::onlineNotification_Postgres() From c92e030a4bf5443c3e0765645ef7bf32130ec4c0 Mon Sep 17 00:00:00 2001 From: Travis LaDuke Date: Fri, 27 Sep 2019 14:50:21 -0700 Subject: [PATCH 109/362] Create a bash completion script. Just adding it to the repo, but it still needs to be dealt with during install. Probably put it in $ZT_HOME and then symlink to the proper place for the distro? searches $ZT_HOME/networks.d/ for network IDs searches HISTORY for 16 digit numbers that look like network IDs. --- zerotier-cli-completion.bash | 57 ++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 zerotier-cli-completion.bash diff --git a/zerotier-cli-completion.bash b/zerotier-cli-completion.bash new file mode 100644 index 000000000..cd6da0d9a --- /dev/null +++ b/zerotier-cli-completion.bash @@ -0,0 +1,57 @@ +#compdef zerotier-cli +#autoload + + +_get_network_ids () +{ + if [[ "$OSTYPE" == "darwin"* ]]; then + COMPREPLY=($(compgen -W "$(ls -1 /Library/Application\ Support/ZeroTier/One/networks.d | cut -c 1-16)" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(ls -1 /var/lib/zerotier-one/networks.d | cut -c 1-16)" -- ${cur})) + fi +} + +_get_network_ids_from_history () +{ + COMPREPLY=($(compgen -W "$(fc -l -1000 -1 | sed -n 's/.*\([[:xdigit:]]\{16\}\).*/\1/p')" -- ${cur})) +} + +_zerotier-cli_completions() +{ + local cur prev + + cur=${COMP_WORDS[COMP_CWORD]} + prev=${COMP_WORDS[COMP_CWORD-1]} + + case ${COMP_CWORD} in + 1) + COMPREPLY=($(compgen -W "info listpeers peers listnetworks join leave set get listmoons orbit deorbit" -- ${cur})) + ;; + 2) + case ${prev} in + leave) + _get_network_ids + ;; + join) + _get_network_ids_from_history + ;; + set) + _get_network_ids + ;; + get) + _get_network_ids + ;; + *) + COMPREPLY=() + ;; + esac + ;; + *) + COMPREPLY=() + ;; + esac +} + +complete -F _zerotier-cli_completions zerotier-cli + + From 5b700fa4973029fb877b1c80b070f74e085ebd0c Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 27 Jul 2020 18:37:45 -0700 Subject: [PATCH 110/362] println for which notification stream the controller is listening to --- controller/PostgreSQL.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index d89b77814..0ea404559 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -795,6 +795,7 @@ void PostgreSQL::membersDbWatcher() void PostgreSQL::_membersWatcher_Postgres(PGconn *conn) { char buf[11] = {0}; std::string cmd = "LISTEN member_" + std::string(_myAddress.toString(buf)); + fprintf(stderr, "Listening to member stream: %s\n", cmd.c_str()); PGresult *res = PQexec(conn, cmd.c_str()); if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "LISTEN command failed: %s\n", PQresultErrorMessage(res)); @@ -836,7 +837,7 @@ void PostgreSQL::_membersWatcher_Postgres(PGconn *conn) { void PostgreSQL::_membersWatcher_Redis() { char buf[11] = {0}; std::string key = "member-stream:{" + std::string(_myAddress.toString(buf)) + "}"; - + fprintf(stderr, "Listening to member stream: %s\n", key.c_str()); while (_run == 1) { try { json tmp; From 9f4985b11a5a4f93c9435094adfce378d573c7a6 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 27 Jul 2020 23:01:12 -0700 Subject: [PATCH 111/362] Add basic bond health status reporting (listbonds) --- include/ZeroTierOne.h | 35 +++++++++++++++++++---- node/Bond.cpp | 51 ++++++++++++++++++++++++++++++++++ node/Bond.hpp | 19 +++++++++++++ node/Node.cpp | 3 ++ one.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++ service/OneService.cpp | 34 +++++++++++++++++++++++ 6 files changed, 199 insertions(+), 6 deletions(-) diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index afa75c290..e1c8f7df3 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -1475,17 +1475,40 @@ typedef struct enum ZT_PeerRole role; /** - * Number of paths (size of paths[]) - */ - unsigned int pathCount; - - /** - * Whether multiple paths to this peer are bonded + * Whether a multi-link bond has formed */ bool isBonded; + /** + * The bonding policy used to bond to this peer + */ int bondingPolicy; + /** + * The health status of the bond to this peer + */ + bool isHealthy; + + /** + * The number of links that comprise the bond to this peer that are considered alive + */ + int numAliveLinks; + + /** + * The number of links that comprise the bond to this peer + */ + int numTotalLinks; + + /** + * The user-specified bond template name + */ + char customBondName[32]; + + /** + * Number of paths (size of paths[]) + */ + unsigned int pathCount; + /** * Known network paths to peer */ diff --git a/node/Bond.cpp b/node/Bond.cpp index 609d62bc6..e7ae33c85 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -730,6 +730,9 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) { //fprintf(stderr, "%lu curateBond (rebuildBond=%d), _numBondedPaths=%d\n", ((now - RR->bc->getBondStartTime())), rebuildBond, _numBondedPaths); char pathStr[128]; + + uint8_t tmpNumAliveLinks = 0; + uint8_t tmpNumTotalLinks = 0; /** * Update path states */ @@ -737,6 +740,10 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) if (!_paths[i]) { continue; } + tmpNumTotalLinks++; + if (_paths[i]->alive(now, true)) { + tmpNumAliveLinks++; + } bool currEligibility = _paths[i]->eligible(now,_ackSendInterval); //_paths[i]->address().toString(pathStr); //fprintf(stderr, "\n\n%ld path eligibility (for %s, %s):\n", (RR->node->now() - RR->bc->getBondStartTime()), getLink(_paths[i])->ifname().c_str(), pathStr); @@ -764,6 +771,46 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) } _paths[i]->_lastEligibilityState = currEligibility; } + _numAliveLinks = tmpNumAliveLinks; + _numTotalLinks = tmpNumTotalLinks; + + /* Determine health status to report to user */ + + bool tmpHealthStatus = true; + + if (_bondingPolicy == ZT_BONDING_POLICY_ACTIVE_BACKUP) { + if (_numAliveLinks < 2) { + // Considered healthy if there is at least one failover link + tmpHealthStatus = false; + } + } + if (_bondingPolicy == ZT_BONDING_POLICY_BROADCAST) { + if (_numAliveLinks < 1) { + // Considerd healthy if we're able to send frames at all + tmpHealthStatus = false; + } + } + if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR) { + if (_numAliveLinks < _numTotalLinks) { + // Considerd healthy if all known paths are alive, this should be refined to account for user bond config settings + tmpHealthStatus = false; + } + } + if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR) { + if (_numAliveLinks < _numTotalLinks) { + // Considerd healthy if all known paths are alive, this should be refined to account for user bond config settings + tmpHealthStatus = false; + } + } + if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { + if (_numAliveLinks < _numTotalLinks) { + // Considerd healthy if all known paths are alive, this should be refined to account for user bond config settings + tmpHealthStatus = false; + } + } + + _isHealthy = tmpHealthStatus; + /** * Curate the set of paths that are part of the bond proper. Selects a single path * per logical link according to eligibility and user-specified constraints. @@ -1509,6 +1556,10 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _lastCheckUserPreferences = 0; _lastBackgroundTaskCheck = 0; + _isHealthy = false; + _numAliveLinks = 0; + _numTotalLinks = 0; + _downDelay = 0; _upDelay = 0; _allowFlowHashing=false; diff --git a/node/Bond.hpp b/node/Bond.hpp index 4eee20f36..c87caf281 100644 --- a/node/Bond.hpp +++ b/node/Bond.hpp @@ -485,6 +485,21 @@ public: */ inline uint8_t getPolicy() { return _bondingPolicy; } + /** + * @return the health status of the bond + */ + inline bool isHealthy() { return _isHealthy; } + + /** + * @return the number of links comprising this bond which are considered alive + */ + inline uint8_t getNumAliveLinks() { return _numAliveLinks; }; + + /** + * @return the number of links comprising this bond + */ + inline uint8_t getNumTotalLinks() { return _numTotalLinks; } + /** * * @param allowFlowHashing @@ -626,6 +641,10 @@ private: uint16_t _maxAcceptablePacketDelayVariance; uint8_t _minAcceptableAllocation; + bool _isHealthy; + uint8_t _numAliveLinks; + uint8_t _numTotalLinks; + /** * Default initial punishment inflicted on misbehaving paths. Punishment slowly * drains linearly. For each eligibility change the remaining punishment is doubled. diff --git a/node/Node.cpp b/node/Node.cpp index 16484dac0..e4b0a0735 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -513,6 +513,9 @@ ZT_PeerList *Node::peers() const if (pi->second->bond()) { p->isBonded = pi->second->bond(); p->bondingPolicy = pi->second->bond()->getPolicy(); + p->isHealthy = pi->second->bond()->isHealthy(); + p->numAliveLinks = pi->second->bond()->getNumAliveLinks(); + p->numTotalLinks = pi->second->bond()->getNumTotalLinks(); } } diff --git a/one.cpp b/one.cpp index 99a3a575b..27f6a06ae 100644 --- a/one.cpp +++ b/one.cpp @@ -72,6 +72,8 @@ #include "osdep/Http.hpp" #include "osdep/Thread.hpp" +#include "node/BondController.hpp" + #include "service/OneService.hpp" #include "ext/json/json.hpp" @@ -467,6 +469,67 @@ static int cli(int argc,char **argv) printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); return 1; } + } else if (command == "listbonds") { + const unsigned int scode = Http::GET(1024 * 1024 * 16,60000,(const struct sockaddr *)&addr,"/bonds",requestHeaders,responseHeaders,responseBody); + + if (scode == 0) { + printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str()); + return 1; + } + + nlohmann::json j; + try { + j = OSUtils::jsonParse(responseBody); + } catch (std::exception &exc) { + printf("%u %s invalid JSON response (%s)" ZT_EOL_S,scode,command.c_str(),exc.what()); + return 1; + } catch ( ... ) { + printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S,scode,command.c_str()); + return 1; + } + + if (scode == 200) { + if (json) { + printf("%s" ZT_EOL_S,OSUtils::jsonDump(j).c_str()); + } else { + printf(" " ZT_EOL_S); + if (j.is_array()) { + for(unsigned long k=0;k= ZT_BONDING_POLICY_NONE && bondingPolicy <= ZT_BONDING_POLICY_BALANCE_AWARE) { + policyStr = BondController::getPolicyStrByCode(bondingPolicy); + } + + printf("%10s %32s %8s %d/%d" ZT_EOL_S, + OSUtils::jsonString(p ["address"],"-").c_str(), + policyStr.c_str(), + healthStr.c_str(), + numAliveLinks, + numTotalLinks); + } + } + } + } + return 0; + } else { + printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); + return 1; + } } else if (command == "listnetworks") { const unsigned int scode = Http::GET(1024 * 1024 * 16,60000,(const struct sockaddr *)&addr,"/network",requestHeaders,responseHeaders,responseBody); diff --git a/service/OneService.cpp b/service/OneService.cpp index 96c9babbd..c5d8076d4 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -253,6 +253,11 @@ static void _peerToJson(nlohmann::json &pj,const ZT_Peer *peer) pj["version"] = tmp; pj["latency"] = peer->latency; pj["role"] = prole; + pj["isBonded"] = peer->isBonded; + pj["bondingPolicy"] = peer->bondingPolicy; + pj["isHealthy"] = peer->isHealthy; + pj["numAliveLinks"] = peer->numAliveLinks; + pj["numTotalLinks"] = peer->numTotalLinks; nlohmann::json pa = nlohmann::json::array(); for(unsigned int i=0;ipathCount;++i) { @@ -1348,6 +1353,35 @@ public: } else scode = 404; _node->freeQueryResult((void *)pl); } else scode = 500; + } else if (ps[0] == "bonds") { + ZT_PeerList *pl = _node->peers(); + if (pl) { + if (ps.size() == 1) { + // Return [array] of all peers + + res = nlohmann::json::array(); + for(unsigned long i=0;ipeerCount;++i) { + nlohmann::json pj; + _peerToJson(pj,&(pl->peers[i])); + res.push_back(pj); + } + + scode = 200; + } else if (ps.size() == 2) { + // Return a single peer by ID or 404 if not found + + uint64_t wantp = Utils::hexStrToU64(ps[1].c_str()); + for(unsigned long i=0;ipeerCount;++i) { + if (pl->peers[i].address == wantp) { + _peerToJson(res,&(pl->peers[i])); + scode = 200; + break; + } + } + + } else scode = 404; + _node->freeQueryResult((void *)pl); + } else scode = 500; } else { if (_controller) { scode = _controller->handleControlPlaneHttpGET(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); From d098a99d098851f515dd536ed08baad06e484d63 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Fri, 31 Jul 2020 11:42:03 -0700 Subject: [PATCH 112/362] fix memory init issue and another place where dns data needs to be copied --- .gitignore | 1 + node/DNS.hpp | 1 + node/Network.cpp | 6 ++++++ osdep/EthernetTap.hpp | 1 + osdep/MacEthernetTap.cpp | 5 +++++ osdep/MacEthernetTap.hpp | 1 + osdep/MacKextEthernetTap.cpp | 5 +++++ osdep/MacKextEthernetTap.hpp | 2 ++ service/OneService.cpp | 29 +++++++++++++++++++++++++---- 9 files changed, 47 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 62a6c6a17..584bdb6c1 100755 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,4 @@ __pycache__ attic/world/*.c25519 attic/world/mkworld workspace/ +workspace2/ diff --git a/node/DNS.hpp b/node/DNS.hpp index ff685d32d..a770859d8 100644 --- a/node/DNS.hpp +++ b/node/DNS.hpp @@ -43,6 +43,7 @@ public: template static inline void deserializeDNS(const Buffer &b, unsigned int &p, ZT_VirtualNetworkDNS *dns, const unsigned int dnsCount) { + memset(dns, 0, sizeof(ZT_VirtualNetworkDNS)*ZT_MAX_NETWORK_DNS); for(unsigned int i = 0; i < dnsCount; ++i) { char *d = (char*)b.data()+p; memcpy(dns[i].domain, d, 128); diff --git a/node/Network.cpp b/node/Network.cpp index cfdbb3764..76b322af6 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -586,6 +586,7 @@ Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *u if (!_portInitialized) { ZT_VirtualNetworkConfig ctmp; + memset(&ctmp, 0, sizeof(ZT_VirtualNetworkConfig)); _externalConfig(&ctmp); _portError = RR->node->configureVirtualNetworkPort(tPtr,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp); _portInitialized = true; @@ -1426,6 +1427,11 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const ec->multicastSubscriptions[i].mac = _myMulticastGroups[i].mac().toInt(); ec->multicastSubscriptions[i].adi = _myMulticastGroups[i].adi(); } + + ec->dnsCount = _config.dnsCount; + if (ec->dnsCount > 0) { + memcpy(&ec->dns, &_config.dns, sizeof(ZT_VirtualNetworkDNS)); + } } void Network::_sendUpdatesToMembers(void *tPtr,const MulticastGroup *const newMulticastGroup) diff --git a/osdep/EthernetTap.hpp b/osdep/EthernetTap.hpp index db52e6b43..d8de16886 100644 --- a/osdep/EthernetTap.hpp +++ b/osdep/EthernetTap.hpp @@ -53,6 +53,7 @@ public: virtual void setFriendlyName(const char *friendlyName) = 0; virtual void scanMulticastGroups(std::vector &added,std::vector &removed) = 0; virtual void setMtu(unsigned int mtu) = 0; + virtual void setDns(const char *domain, const std::vector &servers) = 0; }; } // namespace ZeroTier diff --git a/osdep/MacEthernetTap.cpp b/osdep/MacEthernetTap.cpp index 028f85568..3db0b3ff7 100644 --- a/osdep/MacEthernetTap.cpp +++ b/osdep/MacEthernetTap.cpp @@ -452,6 +452,11 @@ void MacEthernetTap::threadMain() } } +void MacEthernetTap::setDns(const char *domain, const std::vector &servers) +{ + +} + } // namespace ZeroTier #endif // __APPLE__ diff --git a/osdep/MacEthernetTap.hpp b/osdep/MacEthernetTap.hpp index 6d5ce8941..7945bb408 100644 --- a/osdep/MacEthernetTap.hpp +++ b/osdep/MacEthernetTap.hpp @@ -56,6 +56,7 @@ public: virtual void setFriendlyName(const char *friendlyName); virtual void scanMulticastGroups(std::vector &added,std::vector &removed); virtual void setMtu(unsigned int mtu); + virtual void setDns(const char *domain, const std::vector &servers); void threadMain() throw(); diff --git a/osdep/MacKextEthernetTap.cpp b/osdep/MacKextEthernetTap.cpp index 2325d5943..a04c2ac9b 100644 --- a/osdep/MacKextEthernetTap.cpp +++ b/osdep/MacKextEthernetTap.cpp @@ -687,4 +687,9 @@ void MacKextEthernetTap::threadMain() } } +void MacKextEthernetTap::setDns(const char *domain, const std::vector &servers) +{ + +} + } // namespace ZeroTier diff --git a/osdep/MacKextEthernetTap.hpp b/osdep/MacKextEthernetTap.hpp index f1a6f36fd..d7ea53028 100644 --- a/osdep/MacKextEthernetTap.hpp +++ b/osdep/MacKextEthernetTap.hpp @@ -56,6 +56,8 @@ public: virtual void setFriendlyName(const char *friendlyName); virtual void scanMulticastGroups(std::vector &added,std::vector &removed); virtual void setMtu(unsigned int mtu); + virtual void setDns(const char *domain, const std::vector &servers); + void threadMain() throw(); diff --git a/service/OneService.cpp b/service/OneService.cpp index c5d8076d4..ff8c37c95 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -501,6 +501,7 @@ public: settings.allowManaged = true; settings.allowGlobal = false; settings.allowDefault = false; + memset(&config, 0, sizeof(ZT_VirtualNetworkConfig)); } std::shared_ptr tap; @@ -831,7 +832,7 @@ public: Mutex::Lock _l(_nets_m); for(std::map::iterator n(_nets.begin());n!=_nets.end();++n) { if (n->second.tap) - syncManagedStuff(n->second,false,true); + syncManagedStuff(n->second,false,true,false); } } } @@ -1117,7 +1118,7 @@ public: } if (n->second.tap) - syncManagedStuff(n->second,true,true); + syncManagedStuff(n->second,true,true,true); return true; } @@ -1864,7 +1865,7 @@ public: } // Apply or update managed IPs for a configured network (be sure n.tap exists) - void syncManagedStuff(NetworkState &n,bool syncIps,bool syncRoutes) + void syncManagedStuff(NetworkState &n,bool syncIps,bool syncRoutes, bool syncDns) { char ipbuf[64]; @@ -1984,6 +1985,26 @@ public: #endif } } + + if (syncDns) { + char buf[128]; + if (n.config.dnsCount > ZT_MAX_NETWORK_DNS) { + fprintf(stderr, "ERROR: %d records > max %d. Skipping DNS\n", n.config.dnsCount, ZT_MAX_NETWORK_DNS); + return; + } + fprintf(stderr, "Syncing %d DNS configurations\n", n.config.dnsCount); + for (int i = 0; i < n.config.dnsCount; ++i) { + if (strlen(n.config.dns[i].domain) != 0) { + fprintf(stderr, "Syncing DNS for domain: %s\n", n.config.dns[i].domain); + for (int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { + InetAddress a(n.config.dns[i].server_addr[j]); + if (a.isV4() || a.isV6()) { + fprintf(stderr, "\t Server %d: %s\n", j+1, a.toIpString(buf)); + } + } + } + } + } } // ========================================================================= @@ -2333,7 +2354,7 @@ public: Sleep(10); } #endif - syncManagedStuff(n,true,true); + syncManagedStuff(n,true,true,true); n.tap->setMtu(nwc->mtu); } else { _nets.erase(nwid); From 2e52a1eebfc26321ca1e6473384e5ef8e74ab3fc Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 4 Aug 2020 09:45:45 -0700 Subject: [PATCH 113/362] forgot a couple queries in postgres. trying to pull dns of member not network in embedded network controller also some debug logging --- controller/EmbeddedNetworkController.cpp | 6 +- controller/PostgreSQL.cpp | 101 +++++++++++++++++++++++ service/OneService.cpp | 2 +- 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index b6621f29a..f37e4f776 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -1416,7 +1416,9 @@ void EmbeddedNetworkController::_request( json &tags = network["tags"]; json &memberCapabilities = member["capabilities"]; json &memberTags = member["tags"]; - json &dns = member["dns"]; + json &dns = network["dns"]; + + fprintf(stderr, "DNS Config for Network ID %.16llx: %s\n", nwid, OSUtils::jsonDump(dns, 2).c_str()); if (metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV,0) <= 0) { // Old versions with no rules engine support get an allow everything rule. @@ -1727,6 +1729,8 @@ void EmbeddedNetworkController::_request( ++nc->dnsCount; } } + } else { + dns = json::array(); } // Issue a certificate of ownership for all static IPs diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 0f5fde77b..b8e01e200 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -430,6 +430,46 @@ void PostgreSQL::initializeNetworks(PGconn *conn) config["routes"].push_back(route); } + r2 = PQexecParams(conn, + "SELECT domain, servers FROM ztc_network_dns WHERE network_id = $1", + 1, + NULL, + nwidparam, + NULL, + NULL, + 0); + + if (PQresultStatus(r2) != PGRES_TUPLES_OK) { + fprintf(stderr, "ERROR: Error retrieving DNS settings for network: %s\n", PQresultErrorMessage(r2)); + PQclear(r2); + PQclear(res); + exit(1); + } + + n = PQntuples(r2); + config["dns"] = json::array(); + for (int j = 0; j < n; ++j) { + + json obj; + std::string domain = PQgetvalue(r2, j, 0); + std::string serverList = PQgetvalue(r2, j, 1); + auto servers = json::array(); + if (serverList.rfind("{",0) != std::string::npos) { + serverList = serverList.substr(1, serverList.size()-2); + fprintf(stderr, "Server List: %s\n", serverList.c_str()); + std::stringstream ss(serverList); + while(ss.good()) { + std::string server; + std::getline(ss, server, ','); + servers.push_back(server); + } + } + obj["domain"] = domain; + obj["servers"] = servers; + config["dns"].push_back(obj); + } + fprintf(stderr, "%s\n", OSUtils::jsonDump(config["dns"], 2).c_str()); + PQclear(r2); _networkChanged(empty, config, false); @@ -1424,6 +1464,67 @@ void PostgreSQL::commitThread() continue; } + + res = PQexecParams(conn, + "DELETE FROM ztc_network_dns WHERE network_id = $1", + 1, + NULL, + params, + NULL, + NULL, + 0); + + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + fprintf(stderr, "ERROR: Error updating dns: %s\n", PQresultErrorMessage(res)); + PQclear(res); + PQclear(PQexec(conn, "ROLLBACK")); + delete config; + config = nullptr; + continue; + } + + auto dns = (*config)["dns"]; + err = false; + for (auto i = dns.begin(); i < dns.end(); ++i) { + std::string domain = (*i)["domain"]; + std::stringstream servers; + servers << "{"; + for (auto j = dns["servers"].begin(); j < dns["servers"].end(); ++j) { + servers << *j; + if ( (j+1) != dns["servers"].end()) { + servers << ","; + } + } + servers << "}"; + + const char *p[3] = { + id.c_str(), + domain.c_str(), + servers.str().c_str() + }; + + res = PQexecParams(conn, "INSERT INTO ztc_network_dns (network_id, domain, servers) VALUES ($1, $2, $3)", + 3, + NULL, + p, + NULL, + NULL, + 0); + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + fprintf(stderr, "ERROR: Error updating DNS: %s\n", PQresultErrorMessage(res)); + PQclear(res); + err = true; + break; + } + PQclear(res); + } + if (err) { + PQclear(PQexec(conn, "ROLLBACK")); + delete config; + config = nullptr; + continue; + } + res = PQexec(conn, "COMMIT"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "ERROR: Error committing network update: %s\n", PQresultErrorMessage(res)); diff --git a/service/OneService.cpp b/service/OneService.cpp index ff8c37c95..d9239a16c 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -1992,7 +1992,7 @@ public: fprintf(stderr, "ERROR: %d records > max %d. Skipping DNS\n", n.config.dnsCount, ZT_MAX_NETWORK_DNS); return; } - fprintf(stderr, "Syncing %d DNS configurations\n", n.config.dnsCount); + fprintf(stderr, "Syncing %d DNS configurations for network [%.16llx]\n", n.config.dnsCount, n.config.nwid); for (int i = 0; i < n.config.dnsCount; ++i) { if (strlen(n.config.dns[i].domain) != 0) { fprintf(stderr, "Syncing DNS for domain: %s\n", n.config.dns[i].domain); From 88a3c685fb5a4e756a1d6e280a5780c56c810a93 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 4 Aug 2020 13:52:57 -0700 Subject: [PATCH 114/362] latest --- controller/DB.cpp | 7 +++++++ controller/EmbeddedNetworkController.cpp | 4 ++++ node/Network.cpp | 1 + service/OneService.cpp | 15 +++++++++++++++ 4 files changed, 27 insertions(+) diff --git a/controller/DB.cpp b/controller/DB.cpp index dfa4fa094..a1a72f408 100644 --- a/controller/DB.cpp +++ b/controller/DB.cpp @@ -48,6 +48,8 @@ void DB::initNetwork(nlohmann::json &network) { "type","ACTION_ACCEPT" } }}; } + if (!network.count("dns")) network["dns"] = nlohmann::json::array(); + network["objtype"] = "network"; } @@ -110,6 +112,7 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network) std::lock_guard l2(nw->lock); network = nw->config; } + fprintf(stderr, "DB::get(uint64_t,json): %s %s\n", OSUtils::jsonString(network["nwid"],"").c_str(), OSUtils::jsonDump(network["dns"], 2).c_str()); return true; } @@ -132,6 +135,7 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t mem return false; member = m->second; } + fprintf(stderr, "DB::get(uint64_t,json,uint64_t,mjson): %s %s\n", OSUtils::jsonString(network["nwid"],"").c_str(), OSUtils::jsonDump(network["dns"], 2).c_str()); return true; } @@ -155,6 +159,7 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t mem return false; member = m->second; } + fprintf(stderr, "DB::get(uint64_t,json,uint64_t,mjson,summary): %s %s\n", OSUtils::jsonString(network["nwid"],"").c_str(), OSUtils::jsonDump(network["dns"], 2).c_str()); return true; } @@ -175,6 +180,7 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,std::vectormembers.begin();m!=nw->members.end();++m) members.push_back(m->second); } + fprintf(stderr, "DB::get(uint64_t,json,members): %s %s\n", OSUtils::jsonString(network["nwid"],"").c_str(), OSUtils::jsonDump(network["dns"], 2).c_str()); return true; } @@ -305,6 +311,7 @@ void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool { if (networkConfig.is_object()) { const std::string ids = networkConfig["id"]; + fprintf(stderr, "DB::_networkChanged(): %s\n", OSUtils::jsonDump(networkConfig["dns"]).c_str()); const uint64_t networkId = Utils::hexStrToU64(ids.c_str()); if (networkId) { std::shared_ptr<_Network> nw; diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index f37e4f776..b525895bd 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -1390,6 +1390,7 @@ void EmbeddedNetworkController::_request( nc->mtu = std::max(std::min((unsigned int)OSUtils::jsonInt(network["mtu"],ZT_DEFAULT_MTU),(unsigned int)ZT_MAX_MTU),(unsigned int)ZT_MIN_MTU); nc->multicastLimit = (unsigned int)OSUtils::jsonInt(network["multicastLimit"],32ULL); + std::string rtt(OSUtils::jsonString(member["remoteTraceTarget"],"")); if (rtt.length() == 10) { nc->remoteTraceTarget = Address(Utils::hexStrToU64(rtt.c_str())); @@ -1713,9 +1714,11 @@ void EmbeddedNetworkController::_request( } if(dns.is_array()) { + fprintf(stderr, "dns is array of size %d\n", dns.size()); nc->dnsCount = 0; for(unsigned int p=0; p < dns.size(); ++p) { json &d = dns[p]; + fprintf(stderr, "%s\n", OSUtils::jsonDump(d, 2).c_str()); if (d.is_object()) { std::string domain = OSUtils::jsonString(d["domain"],""); memcpy(nc->dns[nc->dnsCount].domain, domain.c_str(), domain.size()); @@ -1730,6 +1733,7 @@ void EmbeddedNetworkController::_request( } } } else { + fprintf(stderr, "dns is NOT an array\n"); dns = json::array(); } diff --git a/node/Network.cpp b/node/Network.cpp index 76b322af6..97642fa18 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -1429,6 +1429,7 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const } ec->dnsCount = _config.dnsCount; + fprintf(stderr, "Network::_externalConfig dnsCount: %d\n", ec->dnsCount); if (ec->dnsCount > 0) { memcpy(&ec->dns, &_config.dns, sizeof(ZT_VirtualNetworkDNS)); } diff --git a/service/OneService.cpp b/service/OneService.cpp index d9239a16c..f48e90640 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -231,6 +231,21 @@ static void _networkToJson(nlohmann::json &nj,const ZT_VirtualNetworkConfig *nc, mca.push_back(m); } nj["multicastSubscriptions"] = mca; + + nj["dns"] = nlohmann::json::array(); + for(unsigned int i=0;idnsCount;++i) { + nlohmann::json m; + m["domain"] = nc->dns[i].domain; + m["servers"] = nlohmann::json::array(); + for(int j=0;jdns[i].server_addr[j]); + if (a.isV4() || a.isV6()) { + char buf[256]; + m["servers"].push_back(a.toIpString(buf)); + } + } + } } static void _peerToJson(nlohmann::json &pj,const ZT_Peer *peer) From d2708daa8eb3d37cffb553a38d4c616db618a7ee Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 4 Aug 2020 14:33:18 -0700 Subject: [PATCH 115/362] debug output --- controller/PostgreSQL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index b8e01e200..8463c13a8 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -468,7 +468,7 @@ void PostgreSQL::initializeNetworks(PGconn *conn) obj["servers"] = servers; config["dns"].push_back(obj); } - fprintf(stderr, "%s\n", OSUtils::jsonDump(config["dns"], 2).c_str()); + fprintf(stderr, "%s: %s\n", OSUtils::jsonString(config["nwid"], "").c_str(), OSUtils::jsonDump(config["dns"], 2).c_str()); PQclear(r2); From 302ac8fefe67284bcdcc81c50fe3ed2419e96b78 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 5 Aug 2020 14:26:11 -0700 Subject: [PATCH 116/362] DNS config support on macOS --- make-mac.mk | 8 +++-- osdep/MacDNSHelper.hpp | 20 +++++++++++ osdep/MacDNSHelper.mm | 72 ++++++++++++++++++++++++++++++++++++++++ osdep/MacEthernetTap.cpp | 62 ++++++++++++++++++++++++++++++++++ osdep/MacEthernetTap.hpp | 3 ++ service/OneService.cpp | 3 ++ 6 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 osdep/MacDNSHelper.hpp create mode 100644 osdep/MacDNSHelper.mm diff --git a/make-mac.mk b/make-mac.mk index ada65ff77..acf17dd42 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -25,10 +25,10 @@ TIMESTAMP=$(shell date +"%Y%m%d%H%M") DEFS+=-DZT_BUILD_PLATFORM=$(ZT_BUILD_PLATFORM) -DZT_BUILD_ARCHITECTURE=$(ZT_BUILD_ARCHITECTURE) include objects.mk -ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o ext/http-parser/http_parser.o +ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o osdep/MacDNSHelper.o ext/http-parser/http_parser.o ifeq ($(ZT_CONTROLLER),1) - LIBS+=-L/usr/local/opt/libpq/lib -lpq ext/redis-plus-plus-1.1.1/install/macos/lib/libredis++.a ext/hiredis-0.14.1/lib/macos/libhiredis.a + LIBS+=-L/usr/local/opt/libpq/lib -lpq ext/redis-plus-plus-1.1.1/install/macos/lib/libredis++.a ext/hiredis-0.14.1/lib/macos/libhiredis.a -framework SystemConfiguration -framework CoreFoundation DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER_USE_REDIS -DZT_CONTROLLER INCLUDES+=-I/usr/local/opt/libpq/include -Iext/hiredis-0.14.1/include/ -Iext/redis-plus-plus-1.1.1/install/macos/include/sw/ @@ -97,7 +97,11 @@ mac-agent: FORCE $(CC) -Ofast -o MacEthernetTapAgent osdep/MacEthernetTapAgent.c $(CODESIGN) -f -s $(CODESIGN_APP_CERT) MacEthernetTapAgent +osdep/MacDNSHelper.o: osdep/MacDNSHelper.mm + $(CXX) $(CXXFLAGS) -c osdep/MacDNSHelper.mm -o osdep/MacDNSHelper.o + one: $(CORE_OBJS) $(ONE_OBJS) one.o mac-agent + $(CXX) $(CXXFLAGS) -o zerotier-one $(CORE_OBJS) $(ONE_OBJS) one.o $(LIBS) # $(STRIP) zerotier-one ln -sf zerotier-one zerotier-idtool diff --git a/osdep/MacDNSHelper.hpp b/osdep/MacDNSHelper.hpp new file mode 100644 index 000000000..de45a8507 --- /dev/null +++ b/osdep/MacDNSHelper.hpp @@ -0,0 +1,20 @@ +#ifndef MAC_DNS_HELPER +#define MAC_DNS_HELPER + +#include +#include "../node/InetAddress.hpp" + +namespace ZeroTier { + +class MacDNSHelper +{ +public: + static void doTheThing(); + + static void setDNS(uint64_t nwid, const char *domain, const std::vector &servers); + static void removeDNS(uint64_t nwid); +}; + +} + +#endif diff --git a/osdep/MacDNSHelper.mm b/osdep/MacDNSHelper.mm new file mode 100644 index 000000000..75b5fd560 --- /dev/null +++ b/osdep/MacDNSHelper.mm @@ -0,0 +1,72 @@ +#include "MacDNSHelper.hpp" + +#include + +#include + +namespace ZeroTier { + +void MacDNSHelper::doTheThing() +{ + fprintf(stderr, "\n\nDOING THE THING!!\n\n"); +} + +void MacDNSHelper::setDNS(uint64_t nwid, const char *domain, const std::vector &servers) +{ + SCDynamicStoreRef ds = SCDynamicStoreCreate(NULL, CFSTR("zerotier"), NULL, NULL); + + CFStringRef *s = new CFStringRef[4]; + for (unsigned int i = 0; i < servers.size(); ++i) { + char buf[64]; + ZeroTier::InetAddress a = servers[i]; + const char *ipStr = a.toIpString(buf); + s[i] = CFStringCreateWithCString(NULL, ipStr, kCFStringEncodingUTF8); + } + + CFArrayRef serverArray = CFArrayCreate(NULL, (const void**)s, servers.size(), &kCFTypeArrayCallBacks); + + CFStringRef keys[2]; + keys[0] = CFSTR("SupplementalMatchDomains"); + keys[1] = CFSTR("ServerAddresses"); + + CFStringRef cfdomain = CFStringCreateWithCString(NULL, domain, kCFStringEncodingUTF8); + CFArrayRef domainArray = CFArrayCreate(NULL, (const void**)&cfdomain, 1, &kCFTypeArrayCallBacks); + + CFTypeRef values[2]; + values[0] = domainArray; + values[1] = serverArray; + + CFDictionaryRef dict = CFDictionaryCreate(NULL, + (const void**)keys, (const void**)values, 2, &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFShow(dict); + + char buf[256] = {0}; + sprintf(buf, "State:/Network/Service/%.16llx/DNS", nwid); + CFStringRef key = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8); + CFArrayRef list = SCDynamicStoreCopyKeyList(ds, key); + + CFIndex i = 0, j = CFArrayGetCount(list); + bool ret = TRUE; + if (j <= 0) { + fprintf(stderr, "Key '%s' does not exist. Creating.\n", buf); + ret &= SCDynamicStoreAddValue(ds, key, dict); + } else { + fprintf(stderr, "Key '%s' already exists. Updating DNS config.\n", buf); + ret &= SCDynamicStoreSetValue(ds, (CFStringRef)CFArrayGetValueAtIndex(list, i), dict); + } + if (ret) { + fprintf(stderr, "DNS written successfully\n"); + } else { + fprintf(stderr, "Error writing DNS configuration\n"); + } + + delete[] s; +} + +void MacDNSHelper::removeDNS(uint64_t nwid) +{ + +} + +} \ No newline at end of file diff --git a/osdep/MacEthernetTap.cpp b/osdep/MacEthernetTap.cpp index 3db0b3ff7..a48de6758 100644 --- a/osdep/MacEthernetTap.cpp +++ b/osdep/MacEthernetTap.cpp @@ -21,6 +21,7 @@ #include "OSUtils.hpp" #include "MacEthernetTap.hpp" #include "MacEthernetTapAgent.h" +#include "MacDNSHelper.hpp" #include #include @@ -54,6 +55,7 @@ #include #include #include +#include static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); @@ -454,7 +456,67 @@ void MacEthernetTap::threadMain() void MacEthernetTap::setDns(const char *domain, const std::vector &servers) { + MacDNSHelper::doTheThing(); + MacDNSHelper::setDNS(this->_nwid, domain, servers); + // _removeDnsConfig(domain, servers); + // _addDnsConfig(domain, servers); +} +void MacEthernetTap::_removeDnsConfig(const char *domain, const std::vector &servers) +{ + std::string tmpfile = std::tmpnam(nullptr); + std::FILE *remf = std::fopen(tmpfile.c_str(), "w"); + char buf[4096] = {0}; + sprintf(buf, "remove State:/Network/Service/%.16llx/DNS\n", _nwid); + std::fputs(buf, remf); + std::fflush(remf); + std::fclose(remf); + fprintf(stderr, "wrote tmpfile %s\n", tmpfile.c_str()); + long cpid = (long)vfork(); + if (cpid == 0) { + char cmd[1024] = {0}; + sprintf(cmd, "/usr/sbin/scutil < %s", tmpfile.c_str()); + ::execl("/bin/sh", "-c", cmd); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid, &exitcode,0); + if (exitcode) { + throw std::runtime_error("scutil dns config remove error"); + } + } +} + +void MacEthernetTap::_addDnsConfig(const char *domain, const std::vector &servers) +{ + std::string tmpfile = std::tmpnam(nullptr); + std::FILE *addf = std::fopen(tmpfile.c_str(), "w"); + char buf[4096] = {0}; + sprintf(buf, "d.init\n"); + sprintf(buf, "d.add ServerAddresses *"); + for (auto it = servers.begin(); it != servers.end(); ++it) { + char ipbuf[128] = {0}; + sprintf(buf, " %s", it->toIpString(buf)); + } + sprintf(buf, "\n"); + sprintf(buf, "d.add SupplementalMatchDomains * %s\n", domain); + sprintf(buf, "set State:/Network/Service/%.16llx/DNS", _nwid); + std::fputs(buf, addf); + std::fflush(addf); + std::fclose(addf); + fprintf(stderr, "wrote add tmpfile %s\n", tmpfile.c_str()); + long cpid = (long)vfork(); + if (cpid == 0) { + char cmd[1024]; + sprintf(cmd, "'/usr/bin/scutil < %s'", tmpfile.c_str()); + fprintf(stderr, "%s\n", cmd); + ::execl("/bin/sh", "-c", cmd); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid, &exitcode, 0); + if (exitcode) { + throw std::runtime_error("scutil dns config add error"); + } + } } } // namespace ZeroTier diff --git a/osdep/MacEthernetTap.hpp b/osdep/MacEthernetTap.hpp index 7945bb408..9ffbdee56 100644 --- a/osdep/MacEthernetTap.hpp +++ b/osdep/MacEthernetTap.hpp @@ -62,6 +62,9 @@ public: throw(); private: + void _removeDnsConfig(const char *domain, const std::vector &servers); + void _addDnsConfig(const char *domain, const std::vector &servers); + void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); void *_arg; uint64_t _nwid; diff --git a/service/OneService.cpp b/service/OneService.cpp index f48e90640..8b09d236a 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -2011,12 +2011,15 @@ public: for (int i = 0; i < n.config.dnsCount; ++i) { if (strlen(n.config.dns[i].domain) != 0) { fprintf(stderr, "Syncing DNS for domain: %s\n", n.config.dns[i].domain); + std::vector servers; for (int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { InetAddress a(n.config.dns[i].server_addr[j]); if (a.isV4() || a.isV6()) { fprintf(stderr, "\t Server %d: %s\n", j+1, a.toIpString(buf)); + servers.push_back(a); } } + n.tap->setDns(n.config.dns[i].domain, servers); } } } From d6e3164ea10a98a6d65eaac1ef7f1faf142fde37 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 5 Aug 2020 14:42:09 -0700 Subject: [PATCH 117/362] remove initial attempt at dns config via scutil --- osdep/MacEthernetTap.cpp | 59 ---------------------------------------- osdep/MacEthernetTap.hpp | 3 -- 2 files changed, 62 deletions(-) diff --git a/osdep/MacEthernetTap.cpp b/osdep/MacEthernetTap.cpp index a48de6758..fb49bad61 100644 --- a/osdep/MacEthernetTap.cpp +++ b/osdep/MacEthernetTap.cpp @@ -458,65 +458,6 @@ void MacEthernetTap::setDns(const char *domain, const std::vector & { MacDNSHelper::doTheThing(); MacDNSHelper::setDNS(this->_nwid, domain, servers); - // _removeDnsConfig(domain, servers); - // _addDnsConfig(domain, servers); -} - -void MacEthernetTap::_removeDnsConfig(const char *domain, const std::vector &servers) -{ - std::string tmpfile = std::tmpnam(nullptr); - std::FILE *remf = std::fopen(tmpfile.c_str(), "w"); - char buf[4096] = {0}; - sprintf(buf, "remove State:/Network/Service/%.16llx/DNS\n", _nwid); - std::fputs(buf, remf); - std::fflush(remf); - std::fclose(remf); - fprintf(stderr, "wrote tmpfile %s\n", tmpfile.c_str()); - long cpid = (long)vfork(); - if (cpid == 0) { - char cmd[1024] = {0}; - sprintf(cmd, "/usr/sbin/scutil < %s", tmpfile.c_str()); - ::execl("/bin/sh", "-c", cmd); - } else if (cpid > 0) { - int exitcode = -1; - ::waitpid(cpid, &exitcode,0); - if (exitcode) { - throw std::runtime_error("scutil dns config remove error"); - } - } -} - -void MacEthernetTap::_addDnsConfig(const char *domain, const std::vector &servers) -{ - std::string tmpfile = std::tmpnam(nullptr); - std::FILE *addf = std::fopen(tmpfile.c_str(), "w"); - char buf[4096] = {0}; - sprintf(buf, "d.init\n"); - sprintf(buf, "d.add ServerAddresses *"); - for (auto it = servers.begin(); it != servers.end(); ++it) { - char ipbuf[128] = {0}; - sprintf(buf, " %s", it->toIpString(buf)); - } - sprintf(buf, "\n"); - sprintf(buf, "d.add SupplementalMatchDomains * %s\n", domain); - sprintf(buf, "set State:/Network/Service/%.16llx/DNS", _nwid); - std::fputs(buf, addf); - std::fflush(addf); - std::fclose(addf); - fprintf(stderr, "wrote add tmpfile %s\n", tmpfile.c_str()); - long cpid = (long)vfork(); - if (cpid == 0) { - char cmd[1024]; - sprintf(cmd, "'/usr/bin/scutil < %s'", tmpfile.c_str()); - fprintf(stderr, "%s\n", cmd); - ::execl("/bin/sh", "-c", cmd); - } else if (cpid > 0) { - int exitcode = -1; - ::waitpid(cpid, &exitcode, 0); - if (exitcode) { - throw std::runtime_error("scutil dns config add error"); - } - } } } // namespace ZeroTier diff --git a/osdep/MacEthernetTap.hpp b/osdep/MacEthernetTap.hpp index 9ffbdee56..7945bb408 100644 --- a/osdep/MacEthernetTap.hpp +++ b/osdep/MacEthernetTap.hpp @@ -62,9 +62,6 @@ public: throw(); private: - void _removeDnsConfig(const char *domain, const std::vector &servers); - void _addDnsConfig(const char *domain, const std::vector &servers); - void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); void *_arg; uint64_t _nwid; From b9a1719cb175a1d9fa4225ed799ff3b28c038c16 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 5 Aug 2020 14:42:19 -0700 Subject: [PATCH 118/362] Let's make sure to clean up our memory --- osdep/MacDNSHelper.mm | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osdep/MacDNSHelper.mm b/osdep/MacDNSHelper.mm index 75b5fd560..b5fc28f07 100644 --- a/osdep/MacDNSHelper.mm +++ b/osdep/MacDNSHelper.mm @@ -61,12 +61,29 @@ void MacDNSHelper::setDNS(uint64_t nwid, const char *domain, const std::vector Date: Wed, 5 Aug 2020 14:49:45 -0700 Subject: [PATCH 119/362] Remove dns config on shutdown Also add calls to MacDNSHelper to the kext tap --- osdep/MacEthernetTap.cpp | 2 ++ osdep/MacKextEthernetTap.cpp | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osdep/MacEthernetTap.cpp b/osdep/MacEthernetTap.cpp index fb49bad61..ae07e66a3 100644 --- a/osdep/MacEthernetTap.cpp +++ b/osdep/MacEthernetTap.cpp @@ -202,6 +202,8 @@ MacEthernetTap::MacEthernetTap( MacEthernetTap::~MacEthernetTap() { + MacDNSHelper::removeDNS(_nwid); + Mutex::Lock _gl(globalTapCreateLock); ::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit Thread::join(_thread); diff --git a/osdep/MacKextEthernetTap.cpp b/osdep/MacKextEthernetTap.cpp index a04c2ac9b..ea26b53c1 100644 --- a/osdep/MacKextEthernetTap.cpp +++ b/osdep/MacKextEthernetTap.cpp @@ -43,6 +43,8 @@ #include #include +#include "MacDNSHelper.hpp" + // OSX compile fix... in6_var defines this in a struct which namespaces it for C++ ... why?!? struct prf_ra { u_char onlink : 1; @@ -441,6 +443,8 @@ MacKextEthernetTap::MacKextEthernetTap( MacKextEthernetTap::~MacKextEthernetTap() { + MacDNSHelper::removeDNS(_nwid); + ::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit Thread::join(_thread); @@ -689,7 +693,7 @@ void MacKextEthernetTap::threadMain() void MacKextEthernetTap::setDns(const char *domain, const std::vector &servers) { - + MacDNSHelper::setDNS(_nwid, domain, servers); } } // namespace ZeroTier From 6868e98904803a060e6a09f0bcecdc42f64ae9aa Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 5 Aug 2020 16:04:02 -0700 Subject: [PATCH 120/362] Get stuff building on Windows Been a minute since any of this has been updated --- controller/EmbeddedNetworkController.cpp | 2 +- osdep/WindowsEthernetTap.cpp | 5 +++++ osdep/WindowsEthernetTap.hpp | 1 + windows/ZeroTierOne/ZeroTierOne.vcxproj | 7 +++++-- .../ZeroTierOne/ZeroTierOne.vcxproj.filters | 18 ++++++++++++------ 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index b525895bd..ad1db6244 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -585,7 +585,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET( responseBody.reserve((members.size() + 2) * 32); std::string mid; for(auto member=members.begin();member!=members.end();++member) { - mid = (*member)["id"]; + mid = OSUtils::jsonString((*member)["id"], ""); char tmp[128]; OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s\"%s\":%llu",(responseBody.length() > 1) ? "," : "",mid.c_str(),(unsigned long long)OSUtils::jsonInt((*member)["revision"],0)); responseBody.append(tmp); diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp index fb4b498d1..60421198a 100644 --- a/osdep/WindowsEthernetTap.cpp +++ b/osdep/WindowsEthernetTap.cpp @@ -1290,4 +1290,9 @@ void WindowsEthernetTap::_syncIps() } } +void WindowsEthernetTap::setDns(const char* domain, const std::vector& servers) +{ + +} + } // namespace ZeroTier diff --git a/osdep/WindowsEthernetTap.hpp b/osdep/WindowsEthernetTap.hpp index ea08b2fc0..c03c1e414 100644 --- a/osdep/WindowsEthernetTap.hpp +++ b/osdep/WindowsEthernetTap.hpp @@ -97,6 +97,7 @@ public: virtual void setFriendlyName(const char *friendlyName); virtual void scanMulticastGroups(std::vector &added,std::vector &removed); virtual void setMtu(unsigned int mtu); + virtual void setDns(const char* domain, const std::vector &servers); inline const NET_LUID &luid() const { return _deviceLuid; } inline const GUID &guid() const { return _deviceGuid; } diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj b/windows/ZeroTierOne/ZeroTierOne.vcxproj index a91ed7d1a..6759bebf7 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj @@ -33,7 +33,6 @@ - @@ -51,6 +50,8 @@ + + MaxSpeed MaxSpeed @@ -135,7 +136,6 @@ - @@ -162,6 +162,8 @@ + + @@ -240,6 +242,7 @@ true v142 MultiByte + false Application diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters index 78e088c73..d2aba50dc 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters @@ -264,9 +264,6 @@ Source Files\controller - - Source Files\controller - Source Files\controller @@ -276,6 +273,12 @@ Source Files\osdep + + Source Files\node + + + Source Files\node + @@ -515,9 +518,6 @@ Header Files\controller - - Header Files\controller - Header Files\controller @@ -530,6 +530,12 @@ Header Files\osdep + + Header Files\node + + + Header Files\node + From edd960566adc164e0854f0d2379432d178a910af Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Thu, 6 Aug 2020 18:10:40 -0700 Subject: [PATCH 121/362] Improve bond tracing, fix bond initialization bugs, remove vestigial debug code --- node/Bond.cpp | 487 ++++++++++++++++++++++------------------ node/Bond.hpp | 56 +++-- node/BondController.cpp | 25 ++- node/Constants.hpp | 5 + node/Peer.cpp | 8 +- node/Peer.hpp | 4 +- node/Trace.cpp | 21 +- node/Trace.hpp | 5 +- one.cpp | 5 + osdep/OSUtils.hpp | 14 ++ 10 files changed, 353 insertions(+), 277 deletions(-) diff --git a/node/Bond.cpp b/node/Bond.cpp index e7ae33c85..4e555dbd2 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -13,6 +13,8 @@ #include +#include "../osdep/OSUtils.hpp" + #include "Peer.hpp" #include "Bond.hpp" #include "Switch.hpp" @@ -23,12 +25,24 @@ namespace ZeroTier { Bond::Bond(const RuntimeEnvironment *renv, int policy, const SharedPtr& peer) : RR(renv), - _peer(peer) + _peer(peer), + _qosCutoffCount(0), + _ackCutoffCount(0), + _lastAckRateCheck(0), + _lastQoSRateCheck(0), + _lastQualityEstimation(0), + _lastCheckUserPreferences(0), + _lastBackgroundTaskCheck(0), + _lastBondStatusLog(0), + _lastPathNegotiationReceived(0), + _lastPathNegotiationCheck(0), + _lastSentPathNegotiationRequest(0), + _lastFlowStatReset(0), + _lastFlowExpirationCheck(0), + _lastFlowRebalance(0), + _lastFrame(0), + _lastActiveBackupPathChange(0) { - // TODO: Remove for production - _header=false; - _lastLogTS = RR->node->now(); - _lastPrintTS = RR->node->now(); setReasonableDefaults(policy, SharedPtr(), false); _policyAlias = BondController::getPolicyStrByCode(policy); } @@ -43,18 +57,26 @@ Bond::Bond(const RuntimeEnvironment *renv, std::string& basePolicy, std::string& Bond::Bond(const RuntimeEnvironment *renv, SharedPtr originalBond, const SharedPtr& peer) : RR(renv), - _peer(peer) + _peer(peer), + _lastAckRateCheck(0), + _lastQoSRateCheck(0), + _lastQualityEstimation(0), + _lastCheckUserPreferences(0), + _lastBackgroundTaskCheck(0), + _lastBondStatusLog(0), + _lastPathNegotiationReceived(0), + _lastPathNegotiationCheck(0), + _lastFlowStatReset(0), + _lastFlowExpirationCheck(0), + _lastFlowRebalance(0), + _lastFrame(0) { - // TODO: Remove for production - _header=false; - _lastLogTS = RR->node->now(); - _lastPrintTS = RR->node->now(); setReasonableDefaults(originalBond->_bondingPolicy, originalBond, true); } void Bond::nominatePath(const SharedPtr& path, int64_t now) { - char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "nominatePath: %s %s\n", getLink(path)->ifname().c_str(), pathStr); + char traceMsg[256]; char pathStr[128]; path->address().toString(pathStr); Mutex::Lock _l(_paths_m); if (!RR->bc->linkAllowed(_policyAlias, getLink(path))) { return; @@ -62,7 +84,7 @@ void Bond::nominatePath(const SharedPtr& path, int64_t now) bool alreadyPresent = false; for (int i=0; i& path, int64_t now) if (!alreadyPresent) { for (int i=0; ilink = RR->bc->getLinkBySocket(_policyAlias, path->localSocket()); + sprintf(traceMsg, "%s (bond) Nominating link %s/%s to peer %llx. It has now entered its trial period", + OSUtils::humanReadableTimestamp().c_str(), getLink(path)->ifname().c_str(), pathStr, _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); _paths[i]->startTrial(now); break; } @@ -107,7 +130,6 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) */ if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR) { if (!_allowFlowHashing) { - //fprintf(stderr, "_rrPacketsSentOnCurrLink=%d, _numBondedPaths=%d, _rrIdx=%d\n", _rrPacketsSentOnCurrLink, _numBondedPaths, _rrIdx); if (_packetsPerLink == 0) { // Randomly select a path return _paths[_bondedIdx[_freeRandomByte % _numBondedPaths]]; // TODO: Optimize @@ -170,7 +192,10 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) void Bond::recordIncomingInvalidPacket(const SharedPtr& path) { - // char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "recordIncomingInvalidPacket() %s %s\n", getLink(path)->ifname().c_str(), pathStr); + //char traceMsg[256]; char pathStr[128]; path->address().toString(pathStr); + //sprintf(traceMsg, "%s (qos) Invalid packet on link %s/%s from peer %llx", + // OSUtils::humanReadableTimestamp().c_str(), getLink(path)->ifname().c_str(), pathStr, _peer->_id.address().toInt()); + //RR->t->bondStateMessage(NULL, traceMsg); Mutex::Lock _l(_paths_m); for (int i=0; i& path) void Bond::recordOutgoingPacket(const SharedPtr &path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now) { - // char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "recordOutgoingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getLink(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); + //char traceMsg[256]; char pathStr[128]; path->address().toString(pathStr); + //sprintf(traceMsg, "%s (bond) Outgoing packet on link %s/%s to peer %llx", + // OSUtils::humanReadableTimestamp().c_str(), getLink(path)->ifname().c_str(), pathStr, _peer->_id.address().toInt()); + //RR->t->bondStateMessage(NULL, traceMsg); _freeRandomByte += (unsigned char)(packetId >> 8); // Grab entropy to use in path selection logic if (!_shouldCollectPathStatistics) { return; @@ -218,7 +246,10 @@ void Bond::recordOutgoingPacket(const SharedPtr &path, const uint64_t pack void Bond::recordIncomingPacket(const SharedPtr& path, uint64_t packetId, uint16_t payloadLength, Packet::Verb verb, int32_t flowId, int64_t now) { - //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "recordIncomingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getLink(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); + //char traceMsg[256]; char pathStr[128]; path->address().toString(pathStr); + //sprintf(traceMsg, "%s (bond) Incoming packet on link %s/%s from peer %llx [id=%llx, len=%d, verb=%d, flowId=%x]", + // OSUtils::humanReadableTimestamp().c_str(), getLink(path)->ifname().c_str(), pathStr, _peer->_id.address().toInt(), packetId, payloadLength, verb, flowId); + //RR->t->bondStateMessage(NULL, traceMsg); bool isFrame = (verb == Packet::VERB_FRAME || verb == Packet::VERB_EXT_FRAME); bool shouldRecord = (packetId & (ZT_QOS_ACK_DIVISOR - 1) && (verb != Packet::VERB_ACK) @@ -261,8 +292,11 @@ void Bond::recordIncomingPacket(const SharedPtr& path, uint64_t packetId, void Bond::receivedQoS(const SharedPtr& path, int64_t now, int count, uint64_t *rx_id, uint16_t *rx_ts) { - //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "receivedQoS() %s %s\n", getLink(path)->ifname().c_str(), pathStr); Mutex::Lock _l(_paths_m); + //char traceMsg[256]; char pathStr[128]; path->address().toString(pathStr); + //sprintf(traceMsg, "%s (qos) Received QoS packet sampling %d frames from peer %llx via %s/%s", + // OSUtils::humanReadableTimestamp().c_str(), count, _peer->_id.address().toInt(), getLink(path)->ifname().c_str(), pathStr); + //RR->t->bondStateMessage(NULL, traceMsg); // Look up egress times and compute latency values for each record std::map::iterator it; for (int j=0; j& path, int64_t now, int count, uint } } path->qosRecordSize.push(count); - //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "receivedQoS() on path %s %s, count=%d, successful=%d, qosStatsOut.size()=%d\n", getLink(path)->ifname().c_str(), pathStr, count, path->aknowledgedQoSRecordCountSinceLastCheck, path->qosStatsOut.size()); } void Bond::receivedAck(const SharedPtr& path, int64_t now, int32_t ackedBytes) { Mutex::Lock _l(_paths_m); - //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "receivedAck() %s %s, (ackedBytes=%d, lastAckReceived=%lld, ackAge=%lld)\n", getLink(path)->ifname().c_str(), pathStr, ackedBytes, path->lastAckReceived, path->ackAge(now)); + //char traceMsg[256]; char pathStr[128]; path->address().toString(pathStr); + //sprintf(traceMsg, "%s (qos) Received ACK packet for %d bytes from peer %llx via %s/%s", + // OSUtils::humanReadableTimestamp().c_str(), ackedBytes, _peer->_id.address().toInt(), getLink(path)->ifname().c_str(), pathStr); + //RR->t->bondStateMessage(NULL, traceMsg); path->_lastAckReceived = now; path->_unackedBytes = (ackedBytes > path->_unackedBytes) ? 0 : path->_unackedBytes - ackedBytes; int64_t timeSinceThroughputEstimate = (now - path->_lastThroughputEstimation); @@ -300,7 +336,6 @@ void Bond::receivedAck(const SharedPtr& path, int64_t now, int32_t ackedBy int32_t Bond::generateQoSPacket(const SharedPtr& path, int64_t now, char *qosBuffer) { - //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "generateQoSPacket() %s %s\n", getLink(path)->ifname().c_str(), pathStr); int32_t len = 0; std::map::iterator it = path->qosStatsIn.begin(); int i=0; @@ -321,12 +356,15 @@ int32_t Bond::generateQoSPacket(const SharedPtr& path, int64_t now, char * bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) { - //fprintf(stderr, "assignFlowToBondedPath\n"); + char traceMsg[256]; char curPathStr[128]; unsigned int idx = ZT_MAX_PEER_NETWORK_PATHS; if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR) { idx = abs((int)(flow->id() % (_numBondedPaths))); - //fprintf(stderr, "flow->id()=%d, %x, _numBondedPaths=%d, idx=%d\n", flow->id(), flow->id(), _numBondedPaths, idx); + SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, _paths[_bondedIdx[idx]]->localSocket()); + sprintf(traceMsg, "%s (balance-xor) Assigned outgoing flow %x to peer %llx to link %s/%s, %lu active flow(s)\n", + OSUtils::humanReadableTimestamp().c_str(), flow->id(), _peer->_id.address().toInt(), link->ifname().c_str(), curPathStr, _flows.size()); + RR->t->bondStateMessage(NULL, traceMsg); flow->assignPath(_paths[_bondedIdx[idx]],now); ++(_paths[_bondedIdx[idx]]->_assignedFlowCount); } @@ -337,7 +375,9 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) entropy %= _totalBondUnderload; } if (!_numBondedPaths) { - //fprintf(stderr, "no bonded paths for flow assignment\n"); + sprintf(traceMsg, "%s (balance-aware) There are no bonded paths, cannot assign flow %x\n", + OSUtils::humanReadableTimestamp().c_str(), flow->id()); + RR->t->bondStateMessage(NULL, traceMsg); return false; } /* Since there may be scenarios where a path is removed before we can re-estimate @@ -350,18 +390,14 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) totalIncompleteAllocation += _paths[i]->_allocation; } } - //fprintf(stderr, "entropy = %d, totalIncompleteAllocation=%d\n", entropy, totalIncompleteAllocation); entropy %= totalIncompleteAllocation; - //fprintf(stderr, "new entropy = %d\n", entropy); for(unsigned int i=0;ibonded()) { SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); _paths[i]->address().toString(curPathStr); uint8_t probabilitySegment = (_totalBondUnderload > 0) ? _paths[i]->_affinity : _paths[i]->_allocation; - //fprintf(stderr, "i=%2d, entropy=%3d, alloc=%3d, byteload=%4d, segment=%3d, _totalBondUnderload=%3d, ifname=%s, path=%20s\n", i, entropy, _paths[i]->_allocation, _paths[i]->_relativeByteLoad, probabilitySegment, _totalBondUnderload, link->ifname().c_str(), curPathStr); if (entropy <= probabilitySegment) { idx = i; - //fprintf(stderr, "\t is best path\n"); break; } entropy -= probabilitySegment; @@ -375,32 +411,37 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) ++(_paths[idx]->_assignedFlowCount); } else { - //fprintf(stderr, "could not assign flow?\n"); exit(0); // TODO: Remove for production + fprintf(stderr, "could not assign flow?\n"); exit(0); // TODO: Remove for production return false; } } flow->assignedPath()->address().toString(curPathStr); SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, flow->assignedPath()->localSocket()); - //fprintf(stderr, "assigned (tx) flow %x with peer %llx to path %s on %s (idx=%d)\n", flow->id(), _peer->_id.address().toInt(), curPathStr, link->ifname().c_str(), idx); + sprintf(traceMsg, "%s (bond) Assigned outgoing flow %x to peer %llx to link %s/%s, %lu active flow(s)\n", + OSUtils::humanReadableTimestamp().c_str(), flow->id(), _peer->_id.address().toInt(), link->ifname().c_str(), curPathStr, _flows.size()); + RR->t->bondStateMessage(NULL, traceMsg); return true; } SharedPtr Bond::createFlow(const SharedPtr &path, int32_t flowId, unsigned char entropy, int64_t now) { - //fprintf(stderr, "createFlow\n"); + char traceMsg[256]; char curPathStr[128]; // --- if (!_numBondedPaths) { - //fprintf(stderr, "there are no bonded paths, cannot assign flow\n"); + sprintf(traceMsg, "%s (bond) There are no bonded paths to peer %llx, cannot assign flow %x\n", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), flowId); + RR->t->bondStateMessage(NULL, traceMsg); return SharedPtr(); } if (_flows.size() >= ZT_FLOW_MAX_COUNT) { - //fprintf(stderr, "max number of flows reached (%d), forcibly forgetting oldest flow\n", ZT_FLOW_MAX_COUNT); - forgetFlowsWhenNecessary(0,true,now); + sprintf(traceMsg, "%s (bond) Maximum number of flows on bond to peer %llx reached (%d), forcibly forgetting oldest flow\n", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), ZT_FLOW_MAX_COUNT); + RR->t->bondStateMessage(NULL, traceMsg); + forgetFlowsWhenNecessary(0, true, now); } SharedPtr flow = new Flow(flowId, now); _flows[flowId] = flow; - //fprintf(stderr, "new flow %x detected with peer %llx, %lu active flow(s)\n", flowId, _peer->_id.address().toInt(), (_flows.size())); /** * Add a flow with a given Path already provided. This is the case when a packet * is received on a path but no flow exists, in this case we simply assign the path @@ -411,7 +452,9 @@ SharedPtr Bond::createFlow(const SharedPtr &path, int32_t flowId, un path->address().toString(curPathStr); path->_assignedFlowCount++; SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, flow->assignedPath()->localSocket()); - //fprintf(stderr, "assigned (rx) flow %x with peer %llx to path %s on %s\n", flow->id(), _peer->_id.address().toInt(), curPathStr, link->ifname().c_str()); + sprintf(traceMsg, "%s (bond) Assigned incoming flow %x from peer %llx to link %s/%s, %lu active flow(s)\n", + OSUtils::humanReadableTimestamp().c_str(), flow->id(), _peer->_id.address().toInt(), link->ifname().c_str(), curPathStr, _flows.size()); + RR->t->bondStateMessage(NULL, traceMsg); } /** * Add a flow when no path was provided. This means that it is an outgoing packet @@ -425,14 +468,16 @@ SharedPtr Bond::createFlow(const SharedPtr &path, int32_t flowId, un void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) { - //fprintf(stderr, "forgetFlowsWhenNecessary\n"); + char traceMsg[256]; std::map >::iterator it = _flows.begin(); std::map >::iterator oldestFlow = _flows.end(); SharedPtr expiredFlow; if (age) { // Remove by specific age while (it != _flows.end()) { if (it->second->age(now) > age) { - //fprintf(stderr, "forgetting flow %x between this node and %llx, %lu active flow(s)\n", it->first, _peer->_id.address().toInt(), (_flows.size()-1)); + sprintf(traceMsg, "%s (bond) Forgetting flow %x between this node and peer %llx, %lu active flow(s)\n", + OSUtils::humanReadableTimestamp().c_str(), it->first, _peer->_id.address().toInt(), (_flows.size()-1)); + RR->t->bondStateMessage(NULL, traceMsg); it->second->assignedPath()->_assignedFlowCount--; it = _flows.erase(it); } else { @@ -450,7 +495,9 @@ void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) ++it; } if (oldestFlow != _flows.end()) { - //fprintf(stderr, "forgetting oldest flow %x (of age %llu) between this node and %llx, %lu active flow(s)\n", oldestFlow->first, oldestFlow->second->age(now), _peer->_id.address().toInt(), (_flows.size()-1)); + sprintf(traceMsg, "%s (bond) Forgetting oldest flow %x (of age %llu) between this node and peer %llx, %lu active flow(s)\n", + OSUtils::humanReadableTimestamp().c_str(), oldestFlow->first, oldestFlow->second->age(now), _peer->_id.address().toInt(), (_flows.size()-1)); + RR->t->bondStateMessage(NULL, traceMsg); oldestFlow->second->assignedPath()->_assignedFlowCount--; _flows.erase(oldestFlow); } @@ -459,7 +506,7 @@ void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) void Bond::processIncomingPathNegotiationRequest(uint64_t now, SharedPtr &path, int16_t remoteUtility) { - //fprintf(stderr, "processIncomingPathNegotiationRequest\n"); + char traceMsg[256]; if (_abLinkSelectMethod != ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { return; } @@ -471,26 +518,36 @@ void Bond::processIncomingPathNegotiationRequest(uint64_t now, SharedPtr & } SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, path->localSocket()); if (remoteUtility > _localUtility) { - //fprintf(stderr, "peer suggests path, its utility (%d) is greater than ours (%d), we will switch to %s on %s (ls=%llx)\n", remoteUtility, _localUtility, pathStr, link->ifname().c_str(), path->localSocket()); + char pathStr[128]; path->address().toString(pathStr); + sprintf(traceMsg, "%s (bond) Peer %llx suggests using alternate link %s/%s. Remote utility (%d) is GREATER than local utility (%d), switching to said link\n", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), link->ifname().c_str(), pathStr, remoteUtility, _localUtility); + RR->t->bondStateMessage(NULL, traceMsg); negotiatedPath = path; } if (remoteUtility < _localUtility) { - //fprintf(stderr, "peer suggests path, its utility (%d) is less than ours (%d), we will NOT switch to %s on %s (ls=%llx)\n", remoteUtility, _localUtility, pathStr, link->ifname().c_str(), path->localSocket()); + sprintf(traceMsg, "%s (bond) Peer %llx suggests using alternate link %s/%s. Remote utility (%d) is LESS than local utility (%d), not switching\n", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), link->ifname().c_str(), pathStr, remoteUtility, _localUtility); + RR->t->bondStateMessage(NULL, traceMsg); } if (remoteUtility == _localUtility) { - //fprintf(stderr, "peer suggest path, but utility is equal, picking choice made by peer with greater identity.\n"); + sprintf(traceMsg, "%s (bond) Peer %llx suggests using alternate link %s/%s. Remote utility (%d) is equal to local utility (%d)\n", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), link->ifname().c_str(), pathStr, remoteUtility, _localUtility); + RR->t->bondStateMessage(NULL, traceMsg); if (_peer->_id.address().toInt() > RR->node->identity().address().toInt()) { - //fprintf(stderr, "peer identity was greater, going with their choice of %s on %s (ls=%llx)\n", pathStr, link->ifname().c_str(), path->localSocket()); + sprintf(traceMsg, "%s (bond) Agreeing with peer %llx to use alternate link %s/%s\n", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), link->ifname().c_str(), pathStr); + RR->t->bondStateMessage(NULL, traceMsg); negotiatedPath = path; } else { - //fprintf(stderr, "our identity was greater, no change\n"); + sprintf(traceMsg, "%s (bond) Ignoring petition from peer %llx to use alternate link %s/%s\n", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), link->ifname().c_str(), pathStr); + RR->t->bondStateMessage(NULL, traceMsg); } } } void Bond::pathNegotiationCheck(void *tPtr, const int64_t now) { - //fprintf(stderr, "pathNegotiationCheck\n"); char pathStr[128]; int maxInPathIdx = ZT_MAX_PEER_NETWORK_PATHS; int maxOutPathIdx = ZT_MAX_PEER_NETWORK_PATHS; @@ -551,7 +608,10 @@ void Bond::pathNegotiationCheck(void *tPtr, const int64_t now) void Bond::sendPATH_NEGOTIATION_REQUEST(void *tPtr, const SharedPtr &path) { - //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "sendPATH_NEGOTIATION_REQUEST() %s %s\n", getLink(path)->ifname().c_str(), pathStr); + char traceMsg[256]; char pathStr[128]; path->address().toString(pathStr); + sprintf(traceMsg, "%s (bond) Sending link negotiation request to peer %llx via link %s/%s, local utility is %d", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), getLink(path)->ifname().c_str(), pathStr, _localUtility); + RR->t->bondStateMessage(NULL, traceMsg); if (_abLinkSelectMethod != ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { return; } @@ -563,10 +623,9 @@ void Bond::sendPATH_NEGOTIATION_REQUEST(void *tPtr, const SharedPtr &path) } } -void Bond::sendACK(void *tPtr,const SharedPtr &path,const int64_t localSocket, +void Bond::sendACK(void *tPtr, const SharedPtr &path,const int64_t localSocket, const InetAddress &atAddress,int64_t now) { - //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "sendACK() %s %s\n", getLink(path)->ifname().c_str(), pathStr); Packet outp(_peer->_id.address(),RR->identity.address(),Packet::VERB_ACK); int32_t bytesToAck = 0; std::map::iterator it = path->ackStatsIn.begin(); @@ -574,6 +633,10 @@ void Bond::sendACK(void *tPtr,const SharedPtr &path,const int64_t localSoc bytesToAck += it->second; ++it; } + //char traceMsg[256]; char pathStr[128]; path->address().toString(pathStr); + //sprintf(traceMsg, "%s (qos) Sending ACK packet for %d bytes to peer %llx via link %s/%s", + // OSUtils::humanReadableTimestamp().c_str(), bytesToAck, _peer->_id.address().toInt(), getLink(path)->ifname().c_str(), pathStr); + //RR->t->bondStateMessage(NULL, traceMsg); outp.append(bytesToAck); if (atAddress) { outp.armor(_peer->key(),false); @@ -589,7 +652,10 @@ void Bond::sendACK(void *tPtr,const SharedPtr &path,const int64_t localSoc void Bond::sendQOS_MEASUREMENT(void *tPtr,const SharedPtr &path,const int64_t localSocket, const InetAddress &atAddress,int64_t now) { - //char pathStr[128];path->address().toString(pathStr);//fprintf(stderr, "sendQOS() %s %s\n", getLink(path)->ifname().c_str(), pathStr); + //char traceMsg[256]; char pathStr[128]; path->address().toString(pathStr); + //sprintf(traceMsg, "%s (qos) Sending QoS packet to peer %llx via link %s/%s", + // OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), getLink(path)->ifname().c_str(), pathStr); + //RR->t->bondStateMessage(NULL, traceMsg); const int64_t _now = RR->node->now(); Packet outp(_peer->_id.address(),RR->identity.address(),Packet::VERB_QOS_MEASUREMENT); char qosData[ZT_QOS_MAX_PACKET_SIZE]; @@ -618,8 +684,6 @@ void Bond::processBackgroundTasks(void *tPtr, const int64_t now) if (_linkMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC) { int suggestedMonitorInterval = (now - _lastFrame) / 100; _dynamicPathMonitorInterval = std::min(ZT_PATH_HEARTBEAT_PERIOD, ((suggestedMonitorInterval > _bondMonitorInterval) ? suggestedMonitorInterval : _bondMonitorInterval)); - //fprintf(stderr, "_lastFrame=%llu, suggestedMonitorInterval=%d, _dynamicPathMonitorInterval=%d\n", - // (now-_lastFrame), suggestedMonitorInterval, _dynamicPathMonitorInterval); } // TODO: Clarify and generalize this logic if (_linkMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC) { @@ -670,7 +734,7 @@ void Bond::processBackgroundTasks(void *tPtr, const int64_t now) switch (_bondingPolicy) { case ZT_BONDING_POLICY_ACTIVE_BACKUP: - processActiveBackupTasks(now); + processActiveBackupTasks(tPtr, now); break; case ZT_BONDING_POLICY_BROADCAST: break; @@ -691,7 +755,6 @@ void Bond::processBackgroundTasks(void *tPtr, const int64_t now) void Bond::applyUserPrefs() { - //fprintf(stderr, "applyUserPrefs, _minReqPathMonitorInterval=%d\n", RR->bc->minReqPathMonitorInterval()); for(unsigned int i=0;ibc->getBondStartTime())), rebuildBond, _numBondedPaths); + char traceMsg[256]; char pathStr[128]; - uint8_t tmpNumAliveLinks = 0; uint8_t tmpNumTotalLinks = 0; /** @@ -745,24 +807,29 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) tmpNumAliveLinks++; } bool currEligibility = _paths[i]->eligible(now,_ackSendInterval); - //_paths[i]->address().toString(pathStr); - //fprintf(stderr, "\n\n%ld path eligibility (for %s, %s):\n", (RR->node->now() - RR->bc->getBondStartTime()), getLink(_paths[i])->ifname().c_str(), pathStr); - //_paths[i]->printEligible(now,_ackSendInterval); if (currEligibility != _paths[i]->_lastEligibilityState) { _paths[i]->address().toString(pathStr); - //fprintf(stderr, "\n\n%ld path eligibility (for %s, %s) has changed (from %d to %d)\n", (RR->node->now() - RR->bc->getBondStartTime()), getLink(_paths[i])->ifname().c_str(), pathStr, _paths[i]->lastCheckedEligibility, _paths[i]->eligible(now,_ackSendInterval)); + char traceMsg[256]; _paths[i]->address().toString(pathStr); + sprintf(traceMsg, "%s (bond) Eligibility of link %s/%s to peer %llx has changed from %d to %d", + OSUtils::humanReadableTimestamp().c_str(), getLink(_paths[i])->ifname().c_str(), pathStr, _peer->_id.address().toInt(), _paths[i]->_lastEligibilityState, currEligibility); + RR->t->bondStateMessage(NULL, traceMsg); if (currEligibility) { rebuildBond = true; } if (!currEligibility) { _paths[i]->adjustRefractoryPeriod(now, _defaultPathRefractoryPeriod, !currEligibility); if (_paths[i]->bonded()) { - //fprintf(stderr, "the path was bonded, reallocation of its flows will occur soon\n"); + char pathStr[128]; _paths[i]->address().toString(pathStr); + sprintf(traceMsg, "%s (bond) Link %s/%s to peer %llx was bonded, reallocation of its flows will occur soon", + OSUtils::humanReadableTimestamp().c_str(), getLink(_paths[i])->ifname().c_str(), pathStr, _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); rebuildBond = true; _paths[i]->_shouldReallocateFlows = _paths[i]->bonded(); _paths[i]->setBonded(false); } else { - //fprintf(stderr, "the path was not bonded, no consequences\n"); + sprintf(traceMsg, "%s (bond) Link %s/%s to peer %llx was not bonded, no allocation consequences", + OSUtils::humanReadableTimestamp().c_str(), getLink(_paths[i])->ifname().c_str(), pathStr, _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); } } } @@ -808,6 +875,17 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) tmpHealthStatus = false; } } + if (tmpHealthStatus != _isHealthy) { + std::string healthStatusStr; + if (tmpHealthStatus == true) { + healthStatusStr = "HEALTHY"; + } else { + healthStatusStr = "DEGRADED"; + } + sprintf(traceMsg, "%s (bond) Bond to peer %llx is in a %s state (%d/%d links)", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), healthStatusStr.c_str(), _numAliveLinks, _numTotalLinks); + RR->t->bondStateMessage(NULL, traceMsg); + } _isHealthy = tmpHealthStatus; @@ -837,7 +915,6 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) //fprintf(stderr, " link representative path already exists! (%s %s)\n", getLink(_paths[i])->ifname().c_str(), pathStr); if (_paths[i]->preferred() && !_paths[linkMap[link]]->preferred()) { // Override previous choice if preferred - //fprintf(stderr, "overriding since its preferred!\n"); if (_paths[linkMap[link]]->_assignedFlowCount) { _paths[linkMap[link]]->_deprecated = true; } @@ -852,7 +929,6 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) || (!_paths[i]->preferred() && !_paths[linkMap[link]]->preferred())) { if (_paths[i]->preferenceRank() > _paths[linkMap[link]]->preferenceRank()) { // Override if higher preference - //fprintf(stderr, "overriding according to preference preferenceRank!\n"); if (_paths[linkMap[link]]->_assignedFlowCount) { _paths[linkMap[link]]->_deprecated = true; } @@ -882,7 +958,6 @@ void Bond::curateBond(const int64_t now, bool rebuildBond) } } _numBondedPaths = updatedBondedPathCount; - if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_RR) { // Cause a RR reset since the currently used index might no longer be valid _rrPacketsSentOnCurrLink = _packetsPerLink; @@ -956,7 +1031,7 @@ void Bond::estimatePathQuality(const int64_t now) while (it != _paths[i]->qosStatsOut.end()) { int qosRecordTimeout = 5000; //_paths[i]->monitorInterval() * ZT_MULTIPATH_QOS_ACK_INTERVAL_MULTIPLIER * 8; if ((now - it->second) >= qosRecordTimeout) { - //fprintf(stderr, "packetId=%llx was lost\n", it->first); + // Packet was lost it = _paths[i]->qosStatsOut.erase(it); ++currentLostRecords; } else { ++it; } @@ -969,15 +1044,11 @@ void Bond::estimatePathQuality(const int64_t now) pdv[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_latencyVariance, 0, _maxAcceptablePacketDelayVariance, 0, 1)); plr[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_packetLossRatio, 0, _maxAcceptablePacketLossRatio, 0, 1)); per[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_packetErrorRatio, 0, _maxAcceptablePacketErrorRatio, 0, 1)); - //scp[i] = _paths[i]->ipvPref != 0 ? 1.0 : Utils::normalize(_paths[i]->ipScope(), InetAddress::IP_SCOPE_NONE, InetAddress::IP_SCOPE_PRIVATE, 0, 1); // Record bond-wide maximums to determine relative values maxLAT = lat[i] > maxLAT ? lat[i] : maxLAT; maxPDV = pdv[i] > maxPDV ? pdv[i] : maxPDV; maxPLR = plr[i] > maxPLR ? plr[i] : maxPLR; maxPER = per[i] > maxPER ? per[i] : maxPER; - //fprintf(stdout, "EH %d: lat=%8.3f, ltm=%8.3f, pdv=%8.3f, plr=%5.3f, per=%5.3f, thr=%8f, thm=%5.3f, thv=%5.3f, avl=%5.3f, age=%8.2f, scp=%4d, q=%5.3f, qtot=%5.3f, ac=%d if=%s, path=%s\n", - // i, lat[i], ltm[i], pdv[i], plr[i], per[i], thr[i], thm[i], thv[i], avl[i], age[i], scp[i], quality[i], totQuality, alloc[i], getLink(_paths[i])->ifname().c_str(), pathStr); - } // Convert metrics to relative quantities and apply contribution weights for(unsigned int i=0;i 0.0f ? pdv[i] / maxPDV : 0.0f) * _qualityWeights[ZT_QOS_PDV_IDX]); quality[i] += ((maxPLR > 0.0f ? plr[i] / maxPLR : 0.0f) * _qualityWeights[ZT_QOS_PLR_IDX]); quality[i] += ((maxPER > 0.0f ? per[i] / maxPER : 0.0f) * _qualityWeights[ZT_QOS_PER_IDX]); - //quality[i] += (scp[i] * _qualityWeights[ZT_QOS_SCP_IDX]); totQuality += quality[i]; } } - // + // Normalize to 8-bit allocation values for(unsigned int i=0;ibonded()) { alloc[i] = std::ceil((quality[i] / totQuality) * (float)255); _paths[i]->_allocation = alloc[i]; } } - if ((now - _lastLogTS) > 500) { - if (!relevant()) {return;} - //fprintf(stderr, "\n"); - _lastLogTS = now; - int numPlottablePaths=0; - for(unsigned int i=0;iaddress().toString(pathStr); - //fprintf(stderr, "%lu FIN [%d/%d]: pmi=%5d, lat=%4.3f, ltm=%4.3f, pdv=%4.3f, plr=%4.3f, per=%4.3f, thr=%4.3f, thm=%4.3f, thv=%4.3f, age=%4.3f, scp=%4d, q=%4.3f, qtot=%4.3f, ac=%4d, asf=%3d, if=%s, path=%20s, bond=%d, qosout=%d, plrraw=%d\n", - // ((now - RR->bc->getBondStartTime())), i, _numBondedPaths, _paths[i]->monitorInterval, - // lat[i], ltm[i], pdv[i], plr[i], per[i], thr[i], thm[i], thv[i], age[i], scp[i], - // quality[i], totQuality, alloc[i], _paths[i]->assignedFlowCount, getLink(_paths[i])->ifname().c_str(), pathStr, _paths[i]->bonded(), _paths[i]->qosStatsOut.size(), _paths[i]->packetLossRatio); - } - } - if (numPlottablePaths < 2) { - return; - } - if (!_header) { - //fprintf(stdout, "now, bonded, relativeUnderload, flows, "); - for(unsigned int i=0;iaddress().toString(pathStr); - std::string label = std::string((pathStr)) + " " + getLink(_paths[i])->ifname(); - for (int i=0; i<19; ++i) { - //fprintf(stdout, "%s, ", label.c_str()); - } - } - } - _header=true; - } - /* - //fprintf(stdout, "%ld, %d, %d, %d, ",((now - RR->bc->getBondStartTime())),_numBondedPaths,_totalBondUnderload, _flows.size()); - for(unsigned int i=0;iaddress().toString(pathStr); - //fprintf(stdout, "%s, %s, %8.3f, %8.3f, %8.3f, %5.3f, %5.3f, %5.3f, %8f, %5.3f, %5.3f, %d, %5.3f, %d, %d, %d, %d, %d, %d, ", - getLink(_paths[i])->ifname().c_str(), pathStr, _paths[i]->_latencyMean, lat[i],pdv[i], _paths[i]->_packetLossRatio, plr[i],per[i],thr[i],thm[i],thv[i],(now - _paths[i]->lastIn()),quality[i],alloc[i], - _paths[i]->_relativeByteLoad, _paths[i]->_assignedFlowCount, _paths[i]->alive(now, true), _paths[i]->eligible(now,_ackSendInterval), _paths[i]->qosStatsOut.size()); - } - }*/ - //fprintf(stdout, "\n"); - } } void Bond::processBalanceTasks(const int64_t now) { char curPathStr[128]; - // TODO: Generalize int totalAllocation = 0; for (int i=0;ieligible(now,_ackSendInterval) && _paths[i]->_shouldReallocateFlows) { - _paths[i]->address().toString(curPathStr); - //fprintf(stderr, "%d reallocating flows from dead path %s on %s\n", (RR->node->now() - RR->bc->getBondStartTime()), curPathStr, getLink(_paths[i])->ifname().c_str()); + char traceMsg[256]; char pathStr[128]; _paths[i]->address().toString(pathStr); + sprintf(traceMsg, "%s (balance-*) Reallocating flows to peer %llx from dead link %s/%s to surviving links", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), getLink(_paths[i])->ifname().c_str(), pathStr); + RR->t->bondStateMessage(NULL, traceMsg); std::map >::iterator flow_it = _flows.begin(); while (flow_it != _flows.end()) { if (flow_it->second->assignedPath() == _paths[i]) { @@ -1113,7 +1141,10 @@ void Bond::processBalanceTasks(const int64_t now) } if (_paths[i] && _paths[i]->bonded() && _paths[i]->eligible(now,_ackSendInterval) && (_paths[i]->_allocation < minimumAllocationValue) && _paths[i]->_assignedFlowCount) { _paths[i]->address().toString(curPathStr); - //fprintf(stderr, "%d reallocating flows from under-performing path %s on %s\n", (RR->node->now() - RR->bc->getBondStartTime()), curPathStr, getLink(_paths[i])->ifname().c_str()); + char traceMsg[256]; char pathStr[128]; _paths[i]->address().toString(pathStr); + sprintf(traceMsg, "%s (balance-*) Reallocating flows to peer %llx from under-performing link %s/%s\n", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), getLink(_paths[i])->ifname().c_str(), pathStr); + RR->t->bondStateMessage(NULL, traceMsg); std::map >::iterator flow_it = _flows.begin(); while (flow_it != _flows.end()) { if (flow_it->second->assignedPath() == _paths[i]) { @@ -1202,7 +1233,6 @@ void Bond::processBalanceTasks(const int64_t now) void Bond::dequeueNextActiveBackupPath(const uint64_t now) { - //fprintf(stderr, "dequeueNextActiveBackupPath\n"); if (_abFailoverQueue.empty()) { return; } @@ -1216,20 +1246,42 @@ void Bond::dequeueNextActiveBackupPath(const uint64_t now) } } -void Bond::processActiveBackupTasks(const int64_t now) +void Bond::processActiveBackupTasks(void *tPtr, const int64_t now) { - //fprintf(stderr, "%llu processActiveBackupTasks\n", (now - RR->bc->getBondStartTime())); - char pathStr[128]; char prevPathStr[128]; char curPathStr[128]; + char traceMsg[256]; + char pathStr[128]; + char prevPathStr[128]; + char curPathStr[128]; SharedPtr prevActiveBackupPath = _abPath; SharedPtr nonPreferredPath; bool bFoundPrimaryLink = false; + /** + * Generate periodic statuc report + */ + if ((now - _lastBondStatusLog) > ZT_MULTIPATH_BOND_STATUS_INTERVAL) { + _lastBondStatusLog = now; + if (_abPath) { + _abPath->address().toString(curPathStr); + sprintf(traceMsg, "%s (active-backup) Active link to peer %llx is %s/%s, failover queue size is %zu", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), getLink(_abPath)->ifname().c_str(), curPathStr, _abFailoverQueue.size()); + RR->t->bondStateMessage(NULL, traceMsg); + } else { + sprintf(traceMsg, "%s (active-backup) No active link to peer %llx", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); + } + if (_abFailoverQueue.empty()) { + sprintf(traceMsg, "%s (active-backup) Failover queue is empty, bond to peer %llx is NOT currently fault-tolerant", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); + } + } /** * Select initial "active" active-backup link */ if (!_abPath) { - //fprintf(stderr, "%llu no active backup path yet...\n", ((now - RR->bc->getBondStartTime()))); /** * [Automatic mode] * The user has not explicitly specified links or their failover schedule, @@ -1239,13 +1291,16 @@ void Bond::processActiveBackupTasks(const int64_t now) * simply find the next eligible path. */ if (!userHasSpecifiedLinks()) { - //fprintf(stderr, "%llu AB: (auto) user did not specify any links. waiting until we know more\n", ((now - RR->bc->getBondStartTime()))); + sprintf(traceMsg, "%s (active-backup) No links to peer %llx specified. Searching...", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt()); RR->t->bondStateMessage(NULL, traceMsg); for (int i=0; ieligible(now,_ackSendInterval)) { _paths[i]->address().toString(curPathStr); SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); if (link) { - //fprintf(stderr, "%llu AB: (initial) [%d] found eligible path %s on: %s\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, link->ifname().c_str()); + sprintf(traceMsg, "%s (active-backup) Found eligible link %s/%s to peer %llx", + OSUtils::humanReadableTimestamp().c_str(), getLink(_paths[i])->ifname().c_str(), curPathStr, _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); } _abPath = _paths[i]; break; @@ -1257,9 +1312,8 @@ void Bond::processActiveBackupTasks(const int64_t now) * The user has specified links or failover rules that the bonding policy should adhere to. */ else if (userHasSpecifiedLinks()) { - //fprintf(stderr, "%llu AB: (manual) no active backup link, checking local.conf\n", ((now - RR->bc->getBondStartTime()))); if (userHasSpecifiedPrimaryLink()) { - //fprintf(stderr, "%llu AB: (manual) user has specified primary link, looking for it.\n", ((now - RR->bc->getBondStartTime()))); + //sprintf(traceMsg, "%s (active-backup) Checking local.conf for user-specified primary link\n", OSUtils::humanReadableTimestamp().c_str()); for (int i=0; ieligible(now,_ackSendInterval) && link->primary()) { if (!_paths[i]->preferred()) { _paths[i]->address().toString(curPathStr); - //fprintf(stderr, "%llu AB: (initial) [%d] found path on primary link, taking note in case we don't find a preferred path\n", ((now - RR->bc->getBondStartTime())), i); + // Found path on primary link, take note in case we don't find a preferred path nonPreferredPath = _paths[i]; bFoundPrimaryLink = true; } if (_paths[i]->preferred()) { _abPath = _paths[i]; _abPath->address().toString(curPathStr); - SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); - if (link) { - //fprintf(stderr, "%llu AB: (initial) [%d] found preferred path %s on primary link: %s\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, link->ifname().c_str()); - } + SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, _paths[i]->localSocket()); bFoundPrimaryLink = true; - break; + break; // Found preferred path %s on primary link } } } @@ -1288,23 +1339,31 @@ void Bond::processActiveBackupTasks(const int64_t now) _abPath->address().toString(curPathStr); SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _abPath->localSocket()); if (link) { - //fprintf(stderr, "%llu AB: (initial) found preferred primary path: %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, link->ifname().c_str()); + sprintf(traceMsg, "%s (active-backup) Found preferred primary link %s/%s to peer %llx", + OSUtils::humanReadableTimestamp().c_str(), getLink(_abPath)->ifname().c_str(), curPathStr, _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); } } else { if (bFoundPrimaryLink && nonPreferredPath) { - //fprintf(stderr, "%llu AB: (initial) found a non-preferred primary path\n", ((now - RR->bc->getBondStartTime()))); + sprintf(traceMsg, "%s (active-backup) Found non-preferred primary link to peer %llx", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); _abPath = nonPreferredPath; } } if (!_abPath) { - //fprintf(stderr, "%llu AB: (initial) designated primary link is not yet ready\n", ((now - RR->bc->getBondStartTime()))); - // TODO: Should fail-over to specified backup or just wait? + sprintf(traceMsg, "%s (active-backup) Designated primary link to peer %llx is not yet ready", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); + // TODO: Should wait for some time (failover interval?) and then swtich to spare link } } else if (!userHasSpecifiedPrimaryLink()) { int _abIdx = ZT_MAX_PEER_NETWORK_PATHS; - //fprintf(stderr, "%llu AB: (initial) user did not specify primary link, just picking something\n", ((now - RR->bc->getBondStartTime()))); + sprintf(traceMsg, "%s (active-backup) User did not specify a primary link to peer %llx, selecting first available link", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); for (int i=0; ieligible(now,_ackSendInterval)) { _abIdx = i; @@ -1312,13 +1371,16 @@ void Bond::processActiveBackupTasks(const int64_t now) } } if (_abIdx == ZT_MAX_PEER_NETWORK_PATHS) { - //fprintf(stderr, "%llu AB: (initial) unable to find a candidate next-best, no change\n", ((now - RR->bc->getBondStartTime()))); + // Unable to find a candidate next-best, no change } else { _abPath = _paths[_abIdx]; SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, _abPath->localSocket()); if (link) { - //fprintf(stderr, "%llu AB: (initial) selected non-primary link idx=%d, %s on %s\n", ((now - RR->bc->getBondStartTime())), _abIdx, pathStr, link->ifname().c_str()); + _abPath->address().toString(curPathStr); + sprintf(traceMsg, "%s (active-backup) Selected non-primary link %s/%s to peer %llx", + OSUtils::humanReadableTimestamp().c_str(), getLink(_abPath)->ifname().c_str(), curPathStr, _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); } } } @@ -1334,10 +1396,12 @@ void Bond::processActiveBackupTasks(const int64_t now) if ((*it) && !(*it)->eligible(now,_ackSendInterval)) { (*it)->address().toString(curPathStr); SharedPtr link =RR->bc->getLinkBySocket(_policyAlias, (*it)->localSocket()); - if (link) { - //fprintf(stderr, "%llu AB: (fq) %s on %s is now ineligible, removing from failover queue\n", ((now - RR->bc->getBondStartTime())), curPathStr, link->ifname().c_str()); - } it = _abFailoverQueue.erase(it); + if (link) { + sprintf(traceMsg, "%s (active-backup) Link %s/%s to peer %llx is now ineligible, removing from failover queue, there are %zu links in the queue", + OSUtils::humanReadableTimestamp().c_str(), getLink(_abPath)->ifname().c_str(), curPathStr, _peer->_id.address().toInt(), _abFailoverQueue.size()); + RR->t->bondStateMessage(NULL, traceMsg); + } } else { ++it; } @@ -1355,7 +1419,7 @@ void Bond::processActiveBackupTasks(const int64_t now) _paths[i]->_failoverScore = 0; } } - //fprintf(stderr, "AB: (fq) user has specified specific failover instructions, will follow them.\n"); + // Follow user-specified failover instructions for (int i=0; iallowed() || !_paths[i]->eligible(now,_ackSendInterval)) { continue; @@ -1364,21 +1428,17 @@ void Bond::processActiveBackupTasks(const int64_t now) _paths[i]->address().toString(pathStr); int failoverScoreHandicap = _paths[i]->_failoverScore; - if (_paths[i]->preferred()) - { + if (_paths[i]->preferred()) { failoverScoreHandicap += ZT_MULTIPATH_FAILOVER_HANDICAP_PREFERRED; - //fprintf(stderr, "%s on %s ----> %d for preferred\n", pathStr, _paths[i]->ifname().c_str(), failoverScoreHandicap); } if (link->primary()) { // If using "optimize" primary reselect mode, ignore user link designations failoverScoreHandicap += ZT_MULTIPATH_FAILOVER_HANDICAP_PRIMARY; - //fprintf(stderr, "%s on %s ----> %d for primary\n", pathStr, _paths[i]->ifname().c_str(), failoverScoreHandicap); } if (!_paths[i]->_failoverScore) { // If we didn't inherit a failover score from a "parent" that wants to use this path as a failover int newHandicap = failoverScoreHandicap ? failoverScoreHandicap : _paths[i]->_allocation; _paths[i]->_failoverScore = newHandicap; - //fprintf(stderr, "%s on %s ----> %d for allocation\n", pathStr, _paths[i]->ifname().c_str(), newHandicap); } SharedPtr failoverLink; if (link->failoverToLink().length()) { @@ -1390,7 +1450,6 @@ void Bond::processActiveBackupTasks(const int64_t now) _paths[j]->address().toString(pathStr); int inheritedHandicap = failoverScoreHandicap - 10; int newHandicap = _paths[j]->_failoverScore > inheritedHandicap ? _paths[j]->_failoverScore : inheritedHandicap; - //fprintf(stderr, "\thanding down %s on %s ----> %d\n", pathStr, getLink(_paths[j])->ifname().c_str(), newHandicap); if (!_paths[j]->preferred()) { newHandicap--; } @@ -1406,9 +1465,10 @@ void Bond::processActiveBackupTasks(const int64_t now) } } if (!bFoundPathInQueue) { - _paths[i]->address().toString(curPathStr); - //fprintf(stderr, "%llu AB: (fq) [%d] added %s on %s to queue\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, getLink(_paths[i])->ifname().c_str()); _abFailoverQueue.push_front(_paths[i]); + _paths[i]->address().toString(curPathStr); sprintf(traceMsg, "%s (active-backup) Added link %s/%s to peer %llx to failover queue, there are %zu links in the queue", + OSUtils::humanReadableTimestamp().c_str(), getLink(_abPath)->ifname().c_str(), curPathStr, _peer->_id.address().toInt(), _abFailoverQueue.size()); + RR->t->bondStateMessage(NULL, traceMsg); } } } @@ -1451,17 +1511,16 @@ void Bond::processActiveBackupTasks(const int64_t now) } } if (!bFoundPathInQueue) { - _paths[i]->address().toString(curPathStr); - //fprintf(stderr, "%llu AB: (fq) [%d] added %s on %s to queue\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, getLink(_paths[i])->ifname().c_str()); _abFailoverQueue.push_front(_paths[i]); + _paths[i]->address().toString(curPathStr); + sprintf(traceMsg, "%s (active-backup) Added link %s/%s to peer %llx, there are %zu links in the queue", + OSUtils::humanReadableTimestamp().c_str(), getLink(_paths[i])->ifname().c_str(), curPathStr, _peer->_id.address().toInt(), _abFailoverQueue.size()); + RR->t->bondStateMessage(NULL, traceMsg); } } } } _abFailoverQueue.sort(PathQualityComparator()); - if (_abFailoverQueue.empty()) { - //fprintf(stderr, "%llu AB: (fq) the failover queue is empty, the active-backup bond is no longer fault-tolerant\n", ((now - RR->bc->getBondStartTime()))); - } } /** * Short-circuit if we have no queued paths @@ -1473,13 +1532,20 @@ void Bond::processActiveBackupTasks(const int64_t now) * Fulfill primary reselect obligations */ if (_abPath && !_abPath->eligible(now,_ackSendInterval)) { // Implicit ZT_MULTIPATH_RESELECTION_POLICY_FAILURE - _abPath->address().toString(curPathStr); //fprintf(stderr, "%llu AB: (failure) failover event!, active backup path (%s) is no-longer eligible\n", ((now - RR->bc->getBondStartTime())), curPathStr); + _abPath->address().toString(curPathStr); + sprintf(traceMsg, "%s (active-backup) Link %s/%s to peer %llx has failed. Selecting new link from failover queue, there are %zu links in the queue", + OSUtils::humanReadableTimestamp().c_str(), getLink(_abPath)->ifname().c_str(), curPathStr, _peer->_id.address().toInt(), _abFailoverQueue.size()); + RR->t->bondStateMessage(NULL, traceMsg); if (!_abFailoverQueue.empty()) { - //fprintf(stderr, "%llu AB: (failure) there are (%lu) links in queue to choose from...\n", ((now - RR->bc->getBondStartTime())), _abFailoverQueue.size()); dequeueNextActiveBackupPath(now); - _abPath->address().toString(curPathStr); //fprintf(stderr, "%llu AB: (failure) switched to %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, getLink(_abPath)->ifname().c_str()); + _abPath->address().toString(curPathStr); + sprintf(traceMsg, "%s (active-backup) Active link to peer %llx has been switched to %s/%s", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), getLink(_abPath)->ifname().c_str(), curPathStr); + RR->t->bondStateMessage(NULL, traceMsg); } else { - //fprintf(stderr, "%llu AB: (failure) nothing available in the link queue, doing nothing.\n", ((now - RR->bc->getBondStartTime()))); + sprintf(traceMsg, "%s (active-backup) Failover queue is empty. No links to peer %llx to choose from", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); } } /** @@ -1491,17 +1557,23 @@ void Bond::processActiveBackupTasks(const int64_t now) if (_abLinkSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_ALWAYS) { if (_abPath && !getLink(_abPath)->primary() && getLink(_abFailoverQueue.front())->primary()) { - //fprintf(stderr, "%llu AB: (always) switching to available primary\n", ((now - RR->bc->getBondStartTime()))); dequeueNextActiveBackupPath(now); + _abPath->address().toString(curPathStr); + sprintf(traceMsg, "%s (active-backup) Switching back to available primary link %s/%s to peer %llx [linkSelectionMethod = always]", + OSUtils::humanReadableTimestamp().c_str(), getLink(_abPath)->ifname().c_str(), curPathStr, _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); } } if (_abLinkSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_BETTER) { if (_abPath && !getLink(_abPath)->primary()) { - //fprintf(stderr, "%llu AB: (better) active backup has switched to \"better\" primary link according to re-select policy.\n", ((now - RR->bc->getBondStartTime()))); + // Active backup has switched to "better" primary link according to re-select policy. if (getLink(_abFailoverQueue.front())->primary() && (_abFailoverQueue.front()->_failoverScore > _abPath->_failoverScore)) { dequeueNextActiveBackupPath(now); - //fprintf(stderr, "%llu AB: (better) switched back to user-defined primary\n", ((now - RR->bc->getBondStartTime()))); + _abPath->address().toString(curPathStr); + sprintf(traceMsg, "%s (active-backup) Switching back to user-defined primary link %s/%s to peer %llx [linkSelectionMethod = better]", + OSUtils::humanReadableTimestamp().c_str(), getLink(_abPath)->ifname().c_str(), curPathStr, _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); } } } @@ -1512,14 +1584,16 @@ void Bond::processActiveBackupTasks(const int64_t now) if (_abFailoverQueue.front()->_negotiated) { dequeueNextActiveBackupPath(now); _abPath->address().toString(prevPathStr); - //fprintf(stderr, "%llu AB: (optimize) switched to negotiated path %s on %s\n", ((now - RR->bc->getBondStartTime())), prevPathStr, getLink(_abPath)->ifname().c_str()); _lastPathNegotiationCheck = now; + _abPath->address().toString(curPathStr); + sprintf(traceMsg, "%s (active-backup) Switching negotiated link %s/%s to peer %llx [linkSelectionMethod = optimize]", + OSUtils::humanReadableTimestamp().c_str(), getLink(_abPath)->ifname().c_str(), curPathStr, _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); } else { // Try to find a better path and automatically switch to it -- not too often, though. if ((now - _lastActiveBackupPathChange) > ZT_MULTIPATH_MIN_ACTIVE_BACKUP_AUTOFLOP_INTERVAL) { if (!_abFailoverQueue.empty()) { - //fprintf(stderr, "AB: (optimize) there are (%d) links in queue to choose from...\n", _abFailoverQueue.size()); int newFScore = _abFailoverQueue.front()->_failoverScore; int prevFScore = _abPath->_failoverScore; // Establish a minimum switch threshold to prevent flapping @@ -1530,7 +1604,9 @@ void Bond::processActiveBackupTasks(const int64_t now) _abPath->address().toString(prevPathStr); dequeueNextActiveBackupPath(now); _abPath->address().toString(curPathStr); - //fprintf(stderr, "%llu AB: (optimize) switched from %s on %s (fs=%d) to %s on %s (fs=%d)\n", ((now - RR->bc->getBondStartTime())), prevPathStr, getLink(oldPath)->ifname().c_str(), prevFScore, curPathStr, getLink(_abPath)->ifname().c_str(), newFScore); + sprintf(traceMsg, "%s (active-backup) Switching from %s/%s (fscore=%d) to better link %s/%s (fscore=%d) for peer %llx [linkSelectionMethod = optimize]", + OSUtils::humanReadableTimestamp().c_str(), getLink(oldPath)->ifname().c_str(), prevPathStr, prevFScore, getLink(_abPath)->ifname().c_str(), curPathStr, newFScore, _peer->_id.address().toInt()); + RR->t->bondStateMessage(NULL, traceMsg); } } } @@ -1540,6 +1616,11 @@ void Bond::processActiveBackupTasks(const int64_t now) void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool useTemplate) { + // TODO: Remove for production + _header=false; + _lastLogTS = RR->node->now(); + _lastPrintTS = RR->node->now(); + // If invalid bonding policy, try default int _defaultBondingPolicy = BondController::defaultBondingPolicy(); if (policy <= ZT_BONDING_POLICY_NONE || policy > ZT_BONDING_POLICY_BALANCE_AWARE) { @@ -1553,8 +1634,9 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool } _freeRandomByte = 0; - _lastCheckUserPreferences = 0; - _lastBackgroundTaskCheck = 0; + + _userHasSpecifiedPrimaryLink = false; + _userHasSpecifiedFailoverInstructions = false; _isHealthy = false; _numAliveLinks = 0; @@ -1566,22 +1648,15 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _bondMonitorInterval=0; _shouldCollectPathStatistics=false; - // Path negotiation _allowPathNegotiation=false; - _lastPathNegotiationReceived=0; - _lastPathNegotiationCheck=0; _pathNegotiationCutoffCount=0; _localUtility=0; - _lastFlowStatReset=0; - _lastFlowExpirationCheck=0; - _numBondedPaths=0; _rrPacketsSentOnCurrLink=0; _rrIdx=0; - _lastFlowRebalance=0; _totalBondUnderload = 0; _maxAcceptableLatency = 100; @@ -1590,10 +1665,6 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _maxAcceptablePacketErrorRatio = 0.10; _userHasSpecifiedLinkSpeeds=0; - _lastFrame=0; - - - /* ZT_MULTIPATH_FLOW_REBALANCE_STRATEGY_PASSIVE is the most conservative strategy and is least likely to cause unexpected behavior */ _flowRebalanceStrategy = ZT_MULTIPATH_FLOW_REBALANCE_STRATEGY_AGGRESSIVE; @@ -1627,7 +1698,7 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool * Paths are monitored to determine when/if one needs to be added or removed from the rotation */ case ZT_BONDING_POLICY_BALANCE_RR: - _failoverInterval = 500; + _failoverInterval = 3000; _allowFlowHashing = false; _packetsPerLink = 1024; _linkMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; @@ -1646,7 +1717,7 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool * path and where to place the next flow. */ case ZT_BONDING_POLICY_BALANCE_XOR: - _failoverInterval = 500; + _failoverInterval = 3000; _upDelay = _bondMonitorInterval * 2; _allowFlowHashing = true; _linkMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; @@ -1690,19 +1761,6 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _failoverInterval = templateBond->_failoverInterval; _downDelay = templateBond->_downDelay; _upDelay = templateBond->_upDelay; - - /*fprintf(stderr, "TIMERS: strat=%d, fi= %d, bmi= %d, qos= %d, ack= %d, estimateInt= %d, refractory= %d, ud= %d, dd= %d\n", - _linkMonitorStrategy, - _failoverInterval, - _bondMonitorInterval, - _qosSendInterval, - _ackSendInterval, - _qualityEstimationInterval, - _defaultPathRefractoryPeriod, - _upDelay, - _downDelay); - */ - if (templateBond->_linkMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_PASSIVE && templateBond->_failoverInterval != 0) { //fprintf(stderr, "warning: passive path monitoring was specified, this will prevent failovers from happening in a timely manner.\n"); @@ -1710,36 +1768,31 @@ void Bond::setReasonableDefaults(int policy, SharedPtr templateBond, bool _abLinkSelectMethod = templateBond->_abLinkSelectMethod; memcpy(_qualityWeights, templateBond->_qualityWeights, ZT_QOS_WEIGHT_SIZE * sizeof(float)); } - - // - // Second, apply user specified values (only if they make sense) - - /** - * Timer geometries and counters - */ - // TODO: Think more about the maximum - /* - if (originalBond._failoverInterval > 250 && originalBond._failoverInterval < 65535) { - _failoverInterval = originalBond._failoverInterval; - } - else { - //fprintf(stderr, "warning: _failoverInterval (%d) is out of range, using default (%d)\n", originalBond._failoverInterval, _failoverInterval); - } - */ - + /* Set timer geometries */ _bondMonitorInterval = _failoverInterval / 3; BondController::setMinReqPathMonitorInterval(_bondMonitorInterval); _ackSendInterval = _failoverInterval; _qualityEstimationInterval = _failoverInterval * 2; _dynamicPathMonitorInterval = 0; _ackCutoffCount = 0; - _lastAckRateCheck = 0; _qosSendInterval = _bondMonitorInterval * 4; _qosCutoffCount = 0; - _lastQoSRateCheck = 0; - _lastQualityEstimation=0; throughputMeasurementInterval = _ackSendInterval * 2; _defaultPathRefractoryPeriod = 8000; + + char traceMsg[256]; + sprintf(traceMsg, "%s (bond) Bond to peer %llx is configured as (monStrat=%d, fi= %d, bmi= %d, qos= %d, ack= %d, estimateInt= %d, refractory= %d, ud= %d, dd= %d)", + OSUtils::humanReadableTimestamp().c_str(), _peer->_id.address().toInt(), + _linkMonitorStrategy, + _failoverInterval, + _bondMonitorInterval, + _qosSendInterval, + _ackSendInterval, + _qualityEstimationInterval, + _defaultPathRefractoryPeriod, + _upDelay, + _downDelay); + RR->t->bondStateMessage(NULL, traceMsg); } void Bond::setUserQualityWeights(float weights[], int len) diff --git a/node/Bond.hpp b/node/Bond.hpp index c87caf281..c8a89b616 100644 --- a/node/Bond.hpp +++ b/node/Bond.hpp @@ -244,7 +244,7 @@ public: * @param atAddress * @param now Current time */ - void sendACK(void *tPtr,const SharedPtr &path,int64_t localSocket, + void sendACK(void *tPtr, const SharedPtr &path,int64_t localSocket, const InetAddress &atAddress,int64_t now); /** @@ -276,9 +276,10 @@ public: /** * Perform periodic tasks unique to active-backup * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call * @param now Current time */ - void processActiveBackupTasks(int64_t now); + void processActiveBackupTasks(void *tPtr, int64_t now); /** * Switches the active link in an active-backup scenario to the next best during @@ -584,7 +585,6 @@ private: SharedPtr _abPath; // current active path std::list > _abFailoverQueue; uint8_t _abLinkSelectMethod; // link re-selection policy for the primary link in active-backup - uint64_t _lastActiveBackupPathChange; // balance-rr uint8_t _rrIdx; // index to path currently in use during Round Robin operation @@ -602,7 +602,6 @@ private: // dynamic link monitoring uint8_t _linkMonitorStrategy; - uint64_t _lastFrame; uint32_t _dynamicPathMonitorInterval; // path negotiation @@ -611,29 +610,19 @@ private: uint8_t _numSentPathNegotiationRequests; unsigned int _pathNegotiationCutoffCount; bool _allowPathNegotiation; - uint64_t _lastPathNegotiationReceived; - uint64_t _lastSentPathNegotiationRequest; - // timers + /** + * Timers and intervals + */ uint32_t _failoverInterval; uint32_t _qosSendInterval; uint32_t _ackSendInterval; - uint16_t _ackCutoffCount; - uint64_t _lastAckRateCheck; - uint16_t _qosCutoffCount; - uint64_t _lastQoSRateCheck; uint32_t throughputMeasurementInterval; uint32_t _qualityEstimationInterval; - // timestamps - uint64_t _lastCheckUserPreferences; - uint64_t _lastQualityEstimation; - uint64_t _lastFlowStatReset; - uint64_t _lastFlowExpirationCheck; - uint64_t _lastFlowRebalance; - uint64_t _lastPathNegotiationCheck; - uint64_t _lastBackgroundTaskCheck; - + /** + * Acceptable quality thresholds + */ float _maxAcceptablePacketLossRatio; float _maxAcceptablePacketErrorRatio; uint16_t _maxAcceptableLatency; @@ -641,6 +630,9 @@ private: uint16_t _maxAcceptablePacketDelayVariance; uint8_t _minAcceptableAllocation; + /** + * Link state reporting + */ bool _isHealthy; uint8_t _numAliveLinks; uint8_t _numTotalLinks; @@ -666,6 +658,30 @@ private: */ SharedPtr _peer; + /** + * Rate-limit cutoffs + */ + uint16_t _qosCutoffCount; + uint16_t _ackCutoffCount; + + /** + * Recent event timestamps + */ + uint64_t _lastAckRateCheck; + uint64_t _lastQoSRateCheck; + uint64_t _lastQualityEstimation; + uint64_t _lastCheckUserPreferences; + uint64_t _lastBackgroundTaskCheck; + uint64_t _lastBondStatusLog; + uint64_t _lastPathNegotiationReceived; + uint64_t _lastPathNegotiationCheck; + uint64_t _lastSentPathNegotiationRequest; + uint64_t _lastFlowStatReset; + uint64_t _lastFlowExpirationCheck; + uint64_t _lastFlowRebalance; + uint64_t _lastFrame; + uint64_t _lastActiveBackupPathChange; + Mutex _paths_m; Mutex _flows_m; diff --git a/node/BondController.cpp b/node/BondController.cpp index 4fed2befd..066c10442 100644 --- a/node/BondController.cpp +++ b/node/BondController.cpp @@ -11,6 +11,8 @@ */ /****/ +#include "../osdep/OSUtils.hpp" + #include "Constants.hpp" #include "BondController.hpp" #include "Peer.hpp" @@ -51,9 +53,6 @@ void BondController::addCustomLink(std::string& policyAlias, SharedPtr lin if (search == _interfaceToLinkMap[policyAlias].end()) { link->setAsUserSpecified(true); _interfaceToLinkMap[policyAlias].insert(std::pair>(link->ifname(), link)); - } else { - //fprintf(stderr, "link already exists=%s\n", link->ifname().c_str()); - // Link is already defined, overlay user settings } } @@ -82,33 +81,37 @@ SharedPtr BondController::createTransportTriggeredBond(const RuntimeEnviro Mutex::Lock _l(_bonds_m); int64_t identity = peer->identity().address().toInt(); Bond *bond = nullptr; + char traceMsg[128]; if (!_bonds.count(identity)) { std::string policyAlias; - //fprintf(stderr, "new bond, registering for %llx\n", identity); if (!_policyTemplateAssignments.count(identity)) { if (_defaultBondingPolicy) { - //fprintf(stderr, " no assignment, using default (%d)\n", _defaultBondingPolicy); + sprintf(traceMsg, "%s (bond) Creating new default %s bond to peer %llx", + OSUtils::humanReadableTimestamp().c_str(), getPolicyStrByCode(_defaultBondingPolicy).c_str(), identity); RR->t->bondStateMessage(NULL, traceMsg); bond = new Bond(renv, _defaultBondingPolicy, peer); } if (!_defaultBondingPolicy && _defaultBondingPolicyStr.length()) { - //fprintf(stderr, " no assignment, using default custom (%s)\n", _defaultBondingPolicyStr.c_str()); + sprintf(traceMsg, "%s (bond) Creating new default custom %s bond to peer %llx", + OSUtils::humanReadableTimestamp().c_str(), _defaultBondingPolicyStr.c_str(), identity); + RR->t->bondStateMessage(NULL, traceMsg); bond = new Bond(renv, _bondPolicyTemplates[_defaultBondingPolicyStr].ptr(), peer); } } else { - //fprintf(stderr, " assignment found for %llx, using it as a template (%s)\n", identity,_policyTemplateAssignments[identity].c_str()); if (!_bondPolicyTemplates[_policyTemplateAssignments[identity]]) { - //fprintf(stderr, "unable to locate template (%s), ignoring assignment for (%llx), using defaults\n", _policyTemplateAssignments[identity].c_str(), identity); + sprintf(traceMsg, "%s (bond) Creating new bond. Assignment for peer %llx was specified as %s but the bond definition was not found. Using default %s", + OSUtils::humanReadableTimestamp().c_str(), identity, _policyTemplateAssignments[identity].c_str(), getPolicyStrByCode(_defaultBondingPolicy).c_str()); + RR->t->bondStateMessage(NULL, traceMsg); bond = new Bond(renv, _defaultBondingPolicy, peer); } else { + sprintf(traceMsg, "%s (bond) Creating new default bond %s to peer %llx", + OSUtils::humanReadableTimestamp().c_str(), _defaultBondingPolicyStr.c_str(), identity); + RR->t->bondStateMessage(NULL, traceMsg); bond = new Bond(renv, _bondPolicyTemplates[_policyTemplateAssignments[identity]].ptr(), peer); } } } - else { - //fprintf(stderr, "bond already exists for %llx.\n", identity); - } if (bond) { _bonds[identity] = bond; /** diff --git a/node/Constants.hpp b/node/Constants.hpp index 9b1d21f9f..b8437481b 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -376,6 +376,11 @@ */ #define ZT_AQM_DEFAULT_BUCKET 0 +/** + * How often we emit a one-liner bond summary for each peer + */ +#define ZT_MULTIPATH_BOND_STATUS_INTERVAL 30000 + /** * How long before we consider a path to be dead in the general sense. This is * used while searching for default or alternative paths to try in the absence diff --git a/node/Peer.cpp b/node/Peer.cpp index ad3d47106..4936abe6d 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -91,7 +91,7 @@ void Peer::received( break; } - recordIncomingPacket(tPtr, path, packetId, payloadLength, verb, flowId, now); + recordIncomingPacket(path, packetId, payloadLength, verb, flowId, now); if (trustEstablished) { _lastTrustEstablishedPacketReceived = now; @@ -434,7 +434,7 @@ void Peer::tryMemorizedPath(void *tPtr,int64_t now) } } -void Peer::performMultipathStateCheck(int64_t now) +void Peer::performMultipathStateCheck(void *tPtr, int64_t now) { /** * Check for conditions required for multipath bonding and create a bond @@ -481,7 +481,7 @@ unsigned int Peer::doPingAndKeepalive(void *tPtr,int64_t now) unsigned int sent = 0; Mutex::Lock _l(_paths_m); - performMultipathStateCheck(now); + performMultipathStateCheck(tPtr, now); const bool sendFullHello = ((now - _lastSentFullHello) >= ZT_PEER_PING_PERIOD); _lastSentFullHello = now; @@ -597,7 +597,7 @@ void Peer::recordIncomingInvalidPacket(const SharedPtr& path) _bondToPeer->recordIncomingInvalidPacket(path); } -void Peer::recordIncomingPacket(void *tPtr, const SharedPtr &path, const uint64_t packetId, +void Peer::recordIncomingPacket(const SharedPtr &path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now) { if (!_shouldCollectPathStatistics || !_bondToPeer) { diff --git a/node/Peer.hpp b/node/Peer.hpp index 1a2b6abc1..0f08bed32 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -151,7 +151,7 @@ public: * @param flowId Flow ID * @param now Current time */ - void recordIncomingPacket(void *tPtr, const SharedPtr &path, const uint64_t packetId, + void recordIncomingPacket(const SharedPtr &path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now); /** @@ -229,7 +229,7 @@ public: * possible with this peer. This check should be performed early in the life-cycle of the peer * as well as during the process of learning new paths. */ - void performMultipathStateCheck(int64_t now); + void performMultipathStateCheck(void *tPtr, int64_t now); /** * Send pings or keepalives depending on configured timeouts diff --git a/node/Trace.cpp b/node/Trace.cpp index f7175c4c0..10172fc4c 100644 --- a/node/Trace.cpp +++ b/node/Trace.cpp @@ -94,26 +94,9 @@ void Trace::peerConfirmingUnknownPath(void *const tPtr,const uint64_t networkId, } } -void Trace::peerLinkNowRedundant(void *const tPtr,Peer &peer) +void Trace::bondStateMessage(void *const tPtr,char *msg) { - //ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is fully redundant",peer.address().toInt()); -} - -void Trace::peerLinkNoLongerRedundant(void *const tPtr,Peer &peer) -{ - //ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is no longer redundant",peer.address().toInt()); -} - -void Trace::peerLinkAggregateStatistics(void *const tPtr,Peer &peer) -{ - /* - ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is composed of (%d) physical paths %s, has packet delay variance (%.0f ms), mean latency (%.0f ms)", - peer.address().toInt(), - peer.aggregateLinkPhysicalPathCount(), - peer.interfaceListStr(), - peer.computeAggregateLinkPacketDelayVariance(), - peer.computeAggregateLinkMeanLatency()); - */ + ZT_LOCAL_TRACE(tPtr,RR,"%s",msg); } void Trace::peerLearnedNewPath(void *const tPtr,const uint64_t networkId,Peer &peer,const SharedPtr &newPath,const uint64_t packetId) diff --git a/node/Trace.hpp b/node/Trace.hpp index 71169ebbb..f2453bcc7 100644 --- a/node/Trace.hpp +++ b/node/Trace.hpp @@ -109,10 +109,7 @@ public: void peerConfirmingUnknownPath(void *const tPtr,const uint64_t networkId,Peer &peer,const SharedPtr &path,const uint64_t packetId,const Packet::Verb verb); - void peerLinkNowRedundant(void *const tPtr,Peer &peer); - void peerLinkNoLongerRedundant(void *const tPtr,Peer &peer); - - void peerLinkAggregateStatistics(void *const tPtr,Peer &peer); + void bondStateMessage(void *const tPtr,char *msg); void peerLearnedNewPath(void *const tPtr,const uint64_t networkId,Peer &peer,const SharedPtr &newPath,const uint64_t packetId); void peerRedirected(void *const tPtr,const uint64_t networkId,Peer &peer,const SharedPtr &newPath); diff --git a/one.cpp b/one.cpp index 27f6a06ae..8614f6341 100644 --- a/one.cpp +++ b/one.cpp @@ -492,6 +492,7 @@ static int cli(int argc,char **argv) if (json) { printf("%s" ZT_EOL_S,OSUtils::jsonDump(j).c_str()); } else { + bool bFoundBond = false; printf(" " ZT_EOL_S); if (j.is_array()) { for(unsigned long k=0;k resolve(const char *name); + /** + * @return Current time in a human-readable format + */ + static inline std::string humanReadableTimestamp() + { + time_t rawtime; + struct tm * timeinfo; + char buffer [80]; + time (&rawtime); + timeinfo = localtime (&rawtime); + strftime (buffer,80,"%F %T",timeinfo); + return std::string(buffer); + } + /** * @return Current time in milliseconds since epoch */ From d9c4e644de8d3686ee146211f4d68b0bdf802332 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 11 Aug 2020 18:55:42 -0700 Subject: [PATCH 122/362] Windows DNS --- osdep/WinDNSHelper.cpp | 359 ++++++++++++++++++ osdep/WinDNSHelper.hpp | 24 ++ osdep/WindowsEthernetTap.cpp | 28 +- windows/ZeroTierOne/ZeroTierOne.vcxproj | 7 +- .../ZeroTierOne/ZeroTierOne.vcxproj.filters | 15 +- 5 files changed, 427 insertions(+), 6 deletions(-) create mode 100644 osdep/WinDNSHelper.cpp create mode 100644 osdep/WinDNSHelper.hpp diff --git a/osdep/WinDNSHelper.cpp b/osdep/WinDNSHelper.cpp new file mode 100644 index 000000000..ec4c5152f --- /dev/null +++ b/osdep/WinDNSHelper.cpp @@ -0,0 +1,359 @@ +#include "WinDNSHelper.hpp" + +#include +#include + +#include +#include +#include +#include + +#define MAX_KEY_LENGTH 255 +#define MAX_VALUE_NAME 16383 + +namespace ZeroTier +{ + +BOOL RegDelnodeRecurse(HKEY hKeyRoot, LPTSTR lpSubKey) +{ + LPTSTR lpEnd; + LONG lResult; + DWORD dwSize; + TCHAR szName[MAX_PATH]; + HKEY hKey; + FILETIME ftWrite; + + // First, see if we can delete the key without having + // to recurse. + + lResult = RegDeleteKey(hKeyRoot, lpSubKey); + + if (lResult == ERROR_SUCCESS) + return TRUE; + + lResult = RegOpenKeyEx(hKeyRoot, lpSubKey, 0, KEY_READ, &hKey); + + if (lResult != ERROR_SUCCESS) + { + if (lResult == ERROR_FILE_NOT_FOUND) { + printf("Key not found.\n"); + return TRUE; + } + else { + printf("Error opening key.\n"); + return FALSE; + } + } + + // Check for an ending slash and add one if it is missing. + + lpEnd = lpSubKey + lstrlen(lpSubKey); + + if (*(lpEnd - 1) != TEXT('\\')) + { + *lpEnd = TEXT('\\'); + lpEnd++; + *lpEnd = TEXT('\0'); + } + + // Enumerate the keys + + dwSize = MAX_PATH; + lResult = RegEnumKeyEx(hKey, 0, szName, &dwSize, NULL, + NULL, NULL, &ftWrite); + + if (lResult == ERROR_SUCCESS) + { + do { + + *lpEnd = TEXT('\0'); + StringCchCat(lpSubKey, MAX_PATH * 2, szName); + + if (!RegDelnodeRecurse(hKeyRoot, lpSubKey)) { + break; + } + + dwSize = MAX_PATH; + + lResult = RegEnumKeyEx(hKey, 0, szName, &dwSize, NULL, + NULL, NULL, &ftWrite); + + } while (lResult == ERROR_SUCCESS); + } + + lpEnd--; + *lpEnd = TEXT('\0'); + + RegCloseKey(hKey); + + // Try again to delete the key. + + lResult = RegDeleteKey(hKeyRoot, lpSubKey); + + if (lResult == ERROR_SUCCESS) + return TRUE; + + return FALSE; +} + +//************************************************************* +// +// RegDelnode() +// +// Purpose: Deletes a registry key and all its subkeys / values. +// +// Parameters: hKeyRoot - Root key +// lpSubKey - SubKey to delete +// +// Return: TRUE if successful. +// FALSE if an error occurs. +// +//************************************************************* + +BOOL RegDelnode(HKEY hKeyRoot, LPCTSTR lpSubKey) +{ + TCHAR szDelKey[MAX_PATH * 2]; + + StringCchCopy(szDelKey, MAX_PATH * 2, lpSubKey); + return RegDelnodeRecurse(hKeyRoot, szDelKey); + +} +std::vector getSubKeys(const char* key) +{ + std::vector subkeys; + HKEY hKey; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, + key, + 0, + KEY_READ, + &hKey) == ERROR_SUCCESS) { + + TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name + DWORD cbName; // size of name string + TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name + DWORD cchClassName = MAX_PATH; // size of class string + DWORD cSubKeys = 0; // number of subkeys + DWORD cbMaxSubKey; // longest subkey size + DWORD cchMaxClass; // longest class string + DWORD cValues; // number of values for key + DWORD cchMaxValue; // longest value name + DWORD cbMaxValueData; // longest value data + DWORD cbSecurityDescriptor; // size of security descriptor + FILETIME ftLastWriteTime; // last write time + + DWORD i, retCode; + + TCHAR achValue[MAX_VALUE_NAME]; + DWORD cchValue = MAX_VALUE_NAME; + + retCode = RegQueryInfoKey( + hKey, // key handle + achClass, // buffer for class name + &cchClassName, // size of class string + NULL, // reserved + &cSubKeys, // number of subkeys + &cbMaxSubKey, // longest subkey size + &cchMaxClass, // longest class string + &cValues, // number of values for this key + &cchMaxValue, // longest value name + &cbMaxValueData, // longest value data + &cbSecurityDescriptor, // security descriptor + &ftLastWriteTime); // last write time + + fprintf(stderr, "num subkeys: %d\n", cSubKeys); + for (i = 0; i < cSubKeys; ++i) { + cbName = MAX_KEY_LENGTH; + retCode = RegEnumKeyEx( + hKey, + i, + achKey, + &cbName, + NULL, + NULL, + NULL, + &ftLastWriteTime); + if (retCode == ERROR_SUCCESS) { + subkeys.push_back(achKey); + } + } + } + RegCloseKey(hKey); + return subkeys; +} + +std::vector getValueList(const char* key) { + std::vector values; + HKEY hKey; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, + key, + 0, + KEY_READ, + &hKey) == ERROR_SUCCESS) { + + TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name + DWORD cbName; // size of name string + TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name + DWORD cchClassName = MAX_PATH; // size of class string + DWORD cSubKeys = 0; // number of subkeys + DWORD cbMaxSubKey; // longest subkey size + DWORD cchMaxClass; // longest class string + DWORD cValues; // number of values for key + DWORD cchMaxValue; // longest value name + DWORD cbMaxValueData; // longest value data + DWORD cbSecurityDescriptor; // size of security descriptor + FILETIME ftLastWriteTime; // last write time + + DWORD i, retCode; + + TCHAR achValue[MAX_VALUE_NAME]; + DWORD cchValue = MAX_VALUE_NAME; + + retCode = RegQueryInfoKey( + hKey, // key handle + achClass, // buffer for class name + &cchClassName, // size of class string + NULL, // reserved + &cSubKeys, // number of subkeys + &cbMaxSubKey, // longest subkey size + &cchMaxClass, // longest class string + &cValues, // number of values for this key + &cchMaxValue, // longest value name + &cbMaxValueData, // longest value data + &cbSecurityDescriptor, // security descriptor + &ftLastWriteTime); // last write time + + fprintf(stderr, "Num values: %d\n", cValues); + for (i = 0, retCode = ERROR_SUCCESS; i < cValues; ++i) { + cchValue = MAX_VALUE_NAME; + achValue[0] = '\0'; + retCode = RegEnumValue( + hKey, + i, + achValue, + &cchValue, + NULL, + NULL, + NULL, + NULL); + if (retCode == ERROR_SUCCESS) { + values.push_back(achValue); + } + } + } + RegCloseKey(hKey); + return values; +} + +std::pair WinDNSHelper::hasDNSConfig(uint64_t nwid) +{ + char networkStr[20] = { 0 }; + sprintf(networkStr, "%.16llx", nwid); + + const char* baseKey = "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsPolicyConfig"; + auto subkeys = getSubKeys(baseKey); + for (auto it = subkeys.begin(); it != subkeys.end(); ++it) { + char sub[MAX_KEY_LENGTH] = { 0 }; + sprintf(sub, "%s\\%s", baseKey, it->c_str()); + fprintf(stderr, "Checking key: %s\n", sub); + auto dnsRecords = getValueList(sub); + for (auto it2 = dnsRecords.begin(); it2 != dnsRecords.end(); ++it2) { + fprintf(stderr, "\t%s\n", it2->c_str()); + if ((*it2) == "Comment") { + HKEY hKey; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, + sub, + 0, + KEY_READ, + &hKey) == ERROR_SUCCESS) { + + char buf[16384] = { 0 }; + DWORD size = sizeof(buf); + DWORD retCode = RegGetValueA( + HKEY_LOCAL_MACHINE, + sub, + it2->c_str(), + RRF_RT_REG_SZ, + NULL, + &buf, + &size); + if (retCode == ERROR_SUCCESS) { + if (std::string(networkStr) == std::string(buf)) { + RegCloseKey(hKey); + return std::make_pair(true, std::string(sub)); + } + } + else { + + } + } + RegCloseKey(hKey); + } + } + } + + return std::make_pair(false, std::string()); +} + +void WinDNSHelper::setDNS(uint64_t nwid, const char* domain, const std::vector& servers) +{ + auto hasConfig = hasDNSConfig(nwid); + + std::stringstream ss; + for (auto it = servers.begin(); it != servers.end(); ++it) { + char ipaddr[256] = { 0 }; + ss << it->toIpString(ipaddr); + if ((it + 1) != servers.end()) { + ss << ";"; + } + } + std::string serverValue = ss.str(); + + if (hasConfig.first) { + // update existing config + HKEY dnsKey; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, hasConfig.second.c_str(), 0, KEY_READ | KEY_WRITE, &dnsKey) == ERROR_SUCCESS) { + auto retCode = RegSetKeyValueA(dnsKey, NULL, "GenericDNSServers", REG_SZ, serverValue.data(), (DWORD)serverValue.length()); + if (retCode != ERROR_SUCCESS) { + fprintf(stderr, "Error writing dns servers: %d\n", retCode); + } + } + } else { + // add new config + const char* baseKey = "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsPolicyConfig"; + GUID guid; + CoCreateGuid(&guid); + wchar_t guidTmp[128] = { 0 }; + char guidStr[128] = { 0 }; + StringFromGUID2(guid, guidTmp, 128); + wcstombs(guidStr, guidTmp, 128); + char fullKey[MAX_KEY_LENGTH] = { 0 }; + sprintf(fullKey, "%s\\%s", baseKey, guidStr); + HKEY dnsKey; + RegCreateKeyA(HKEY_LOCAL_MACHINE, fullKey, &dnsKey); + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, fullKey, 0, KEY_READ | KEY_WRITE, &dnsKey) == ERROR_SUCCESS) { + char nwString[32] = { 0 }; + sprintf(nwString, "%.16llx", nwid); + RegSetKeyValueA(dnsKey, NULL, "Comment", REG_SZ, nwString, strlen(nwString)); + + DWORD configOpts = 8; + RegSetKeyValueA(dnsKey, NULL, "ConfigOptions", REG_DWORD, &configOpts, sizeof(DWORD)); + RegSetKeyValueA(dnsKey, NULL, "DisplayName", REG_SZ, "", 0); + RegSetKeyValueA(dnsKey, NULL, "GenericDNSServers", REG_SZ, serverValue.data(), serverValue.length()); + RegSetKeyValueA(dnsKey, NULL, "IPSECCARestriction", REG_SZ, "", 0); + std::string d = "." + std::string(domain); + RegSetKeyValueA(dnsKey, NULL, "Name", REG_MULTI_SZ, d.data(), d.length()); + DWORD version = 2; + RegSetKeyValueA(dnsKey, NULL, "Version", REG_DWORD, &version, sizeof(DWORD)); + } + } +} + +void WinDNSHelper::removeDNS(uint64_t nwid) +{ + auto hasConfig = hasDNSConfig(nwid); + if (hasConfig.first) { + RegDelnode(HKEY_LOCAL_MACHINE, hasConfig.second.c_str()); + } +} + +} \ No newline at end of file diff --git a/osdep/WinDNSHelper.hpp b/osdep/WinDNSHelper.hpp new file mode 100644 index 000000000..c42ba0775 --- /dev/null +++ b/osdep/WinDNSHelper.hpp @@ -0,0 +1,24 @@ +#ifndef WIN_DNS_HELPER_H_ +#define WIN_DNS_HELPER_H_ + +#include +#include +#include "../node/InetAddress.hpp" + + +namespace ZeroTier +{ + +class WinDNSHelper +{ +public: + static void setDNS(uint64_t nwid, const char* domain, const std::vector& servers); + static void removeDNS(uint64_t nwid); + +private: + static std::pair hasDNSConfig(uint64_t nwid); +}; + +} + +#endif \ No newline at end of file diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp index 60421198a..17aa99116 100644 --- a/osdep/WindowsEthernetTap.cpp +++ b/osdep/WindowsEthernetTap.cpp @@ -44,6 +44,7 @@ #include "OSUtils.hpp" #include "..\windows\TapDriver6\tap-windows.h" +#include "WinDNSHelper.hpp" #include @@ -473,6 +474,29 @@ WindowsEthernetTap::WindowsEthernetTap( char data[1024]; char tag[24]; + // Initialize COM + HRESULT hres = CoInitializeEx(0, COINIT_MULTITHREADED); + if (FAILED(hres)) { + throw std::runtime_error("WinEthernetTap: COM initialization failed"); + } + + hres = CoInitializeSecurity( + NULL, + -1, + NULL, + NULL, + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_NONE, + NULL + ); + if (FAILED(hres)) { + CoUninitialize(); + throw std::runtime_error("WinEthernetTap: Failed to initialize security"); + } + + // We "tag" registry entries with the network ID to identify persistent devices OSUtils::ztsnprintf(tag,sizeof(tag),"%.16llx",(unsigned long long)nwid); @@ -646,6 +670,8 @@ WindowsEthernetTap::WindowsEthernetTap( WindowsEthernetTap::~WindowsEthernetTap() { + WinDNSHelper::removeDNS(_nwid); + CoUninitialize(); _run = false; ReleaseSemaphore(_injectSemaphore,1,NULL); Thread::join(_thread); @@ -1292,7 +1318,7 @@ void WindowsEthernetTap::_syncIps() void WindowsEthernetTap::setDns(const char* domain, const std::vector& servers) { - + WinDNSHelper::setDNS(_nwid, domain, servers); } } // namespace ZeroTier diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj b/windows/ZeroTierOne/ZeroTierOne.vcxproj index 6759bebf7..69036aa0a 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj @@ -101,6 +101,7 @@ + true @@ -136,6 +137,7 @@ + @@ -207,6 +209,7 @@ + @@ -370,7 +373,7 @@ true - wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) + wbemuuid.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) false "notelemetry.obj" %(AdditionalOptions) @@ -459,7 +462,7 @@ false true true - wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) + wbemuuid.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) false diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters index d2aba50dc..6abe49c0d 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters @@ -90,9 +90,6 @@ Source Files\service - - Source Files\osdep - Source Files\osdep @@ -279,6 +276,12 @@ Source Files\node + + Source Files\osdep + + + Source Files\osdep + @@ -536,6 +539,12 @@ Header Files\node + + Header Files\controller + + + Header Files\osdep + From 137d05e79902f9ddabc148701c77d4f7a5f7ceaa Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 12 Aug 2020 09:14:10 -0700 Subject: [PATCH 123/362] add setDNS stubs for linux/bsd --- osdep/BSDEthernetTap.hpp | 1 + osdep/LinuxEthernetTap.hpp | 1 + osdep/NetBSDEthernetTap.hpp | 1 + 3 files changed, 3 insertions(+) diff --git a/osdep/BSDEthernetTap.hpp b/osdep/BSDEthernetTap.hpp index d99cebef3..134168176 100644 --- a/osdep/BSDEthernetTap.hpp +++ b/osdep/BSDEthernetTap.hpp @@ -54,6 +54,7 @@ public: virtual void setFriendlyName(const char *friendlyName); virtual void scanMulticastGroups(std::vector &added,std::vector &removed); virtual void setMtu(unsigned int mtu); + virtual void setDns(const char *domain, const std::vector &servers) {} void threadMain() throw(); diff --git a/osdep/LinuxEthernetTap.hpp b/osdep/LinuxEthernetTap.hpp index 7503c5231..58c75f709 100644 --- a/osdep/LinuxEthernetTap.hpp +++ b/osdep/LinuxEthernetTap.hpp @@ -54,6 +54,7 @@ public: virtual void setFriendlyName(const char *friendlyName); virtual void scanMulticastGroups(std::vector &added,std::vector &removed); virtual void setMtu(unsigned int mtu); + virtual void setDns(const char *domain, const std::vector &servers) {} void threadMain() throw(); diff --git a/osdep/NetBSDEthernetTap.hpp b/osdep/NetBSDEthernetTap.hpp index 534712e45..9c00c0e25 100644 --- a/osdep/NetBSDEthernetTap.hpp +++ b/osdep/NetBSDEthernetTap.hpp @@ -53,6 +53,7 @@ public: virtual std::string deviceName() const; virtual void setFriendlyName(const char *friendlyName); virtual void scanMulticastGroups(std::vector &added,std::vector &removed); + virtual void setDns(const char *domain, const std::vector &servers) {} void threadMain() throw(); From 30b18d925f793aeac589b57dd036f596c96d20b2 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 12 Aug 2020 09:16:18 -0700 Subject: [PATCH 124/362] clean up some debug logging --- osdep/MacDNSHelper.hpp | 2 -- osdep/MacDNSHelper.mm | 11 +---------- osdep/MacEthernetTap.cpp | 1 - osdep/WinDNSHelper.cpp | 6 ------ 4 files changed, 1 insertion(+), 19 deletions(-) diff --git a/osdep/MacDNSHelper.hpp b/osdep/MacDNSHelper.hpp index de45a8507..7a2902fa5 100644 --- a/osdep/MacDNSHelper.hpp +++ b/osdep/MacDNSHelper.hpp @@ -9,8 +9,6 @@ namespace ZeroTier { class MacDNSHelper { public: - static void doTheThing(); - static void setDNS(uint64_t nwid, const char *domain, const std::vector &servers); static void removeDNS(uint64_t nwid); }; diff --git a/osdep/MacDNSHelper.mm b/osdep/MacDNSHelper.mm index b5fc28f07..1cfe1b265 100644 --- a/osdep/MacDNSHelper.mm +++ b/osdep/MacDNSHelper.mm @@ -6,11 +6,6 @@ namespace ZeroTier { -void MacDNSHelper::doTheThing() -{ - fprintf(stderr, "\n\nDOING THE THING!!\n\n"); -} - void MacDNSHelper::setDNS(uint64_t nwid, const char *domain, const std::vector &servers) { SCDynamicStoreRef ds = SCDynamicStoreCreate(NULL, CFSTR("zerotier"), NULL, NULL); @@ -49,15 +44,11 @@ void MacDNSHelper::setDNS(uint64_t nwid, const char *domain, const std::vector &servers) { - MacDNSHelper::doTheThing(); MacDNSHelper::setDNS(this->_nwid, domain, servers); } diff --git a/osdep/WinDNSHelper.cpp b/osdep/WinDNSHelper.cpp index ec4c5152f..6bcd2b476 100644 --- a/osdep/WinDNSHelper.cpp +++ b/osdep/WinDNSHelper.cpp @@ -36,11 +36,9 @@ BOOL RegDelnodeRecurse(HKEY hKeyRoot, LPTSTR lpSubKey) if (lResult != ERROR_SUCCESS) { if (lResult == ERROR_FILE_NOT_FOUND) { - printf("Key not found.\n"); return TRUE; } else { - printf("Error opening key.\n"); return FALSE; } } @@ -160,7 +158,6 @@ std::vector getSubKeys(const char* key) &cbSecurityDescriptor, // security descriptor &ftLastWriteTime); // last write time - fprintf(stderr, "num subkeys: %d\n", cSubKeys); for (i = 0; i < cSubKeys; ++i) { cbName = MAX_KEY_LENGTH; retCode = RegEnumKeyEx( @@ -222,7 +219,6 @@ std::vector getValueList(const char* key) { &cbSecurityDescriptor, // security descriptor &ftLastWriteTime); // last write time - fprintf(stderr, "Num values: %d\n", cValues); for (i = 0, retCode = ERROR_SUCCESS; i < cValues; ++i) { cchValue = MAX_VALUE_NAME; achValue[0] = '\0'; @@ -254,10 +250,8 @@ std::pair WinDNSHelper::hasDNSConfig(uint64_t nwid) for (auto it = subkeys.begin(); it != subkeys.end(); ++it) { char sub[MAX_KEY_LENGTH] = { 0 }; sprintf(sub, "%s\\%s", baseKey, it->c_str()); - fprintf(stderr, "Checking key: %s\n", sub); auto dnsRecords = getValueList(sub); for (auto it2 = dnsRecords.begin(); it2 != dnsRecords.end(); ++it2) { - fprintf(stderr, "\t%s\n", it2->c_str()); if ((*it2) == "Comment") { HKEY hKey; if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, From 058d88831122756b119a73c94296f25a638acd2e Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 12 Aug 2020 10:00:09 -0700 Subject: [PATCH 125/362] More debug logging cleanup --- controller/DB.cpp | 5 ----- controller/EmbeddedNetworkController.cpp | 5 ----- controller/PostgreSQL.cpp | 2 -- 3 files changed, 12 deletions(-) diff --git a/controller/DB.cpp b/controller/DB.cpp index a1a72f408..775daf1a5 100644 --- a/controller/DB.cpp +++ b/controller/DB.cpp @@ -112,7 +112,6 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network) std::lock_guard l2(nw->lock); network = nw->config; } - fprintf(stderr, "DB::get(uint64_t,json): %s %s\n", OSUtils::jsonString(network["nwid"],"").c_str(), OSUtils::jsonDump(network["dns"], 2).c_str()); return true; } @@ -135,7 +134,6 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t mem return false; member = m->second; } - fprintf(stderr, "DB::get(uint64_t,json,uint64_t,mjson): %s %s\n", OSUtils::jsonString(network["nwid"],"").c_str(), OSUtils::jsonDump(network["dns"], 2).c_str()); return true; } @@ -159,7 +157,6 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t mem return false; member = m->second; } - fprintf(stderr, "DB::get(uint64_t,json,uint64_t,mjson,summary): %s %s\n", OSUtils::jsonString(network["nwid"],"").c_str(), OSUtils::jsonDump(network["dns"], 2).c_str()); return true; } @@ -180,7 +177,6 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,std::vectormembers.begin();m!=nw->members.end();++m) members.push_back(m->second); } - fprintf(stderr, "DB::get(uint64_t,json,members): %s %s\n", OSUtils::jsonString(network["nwid"],"").c_str(), OSUtils::jsonDump(network["dns"], 2).c_str()); return true; } @@ -311,7 +307,6 @@ void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool { if (networkConfig.is_object()) { const std::string ids = networkConfig["id"]; - fprintf(stderr, "DB::_networkChanged(): %s\n", OSUtils::jsonDump(networkConfig["dns"]).c_str()); const uint64_t networkId = Utils::hexStrToU64(ids.c_str()); if (networkId) { std::shared_ptr<_Network> nw; diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index ad1db6244..a505c3edb 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -1419,8 +1419,6 @@ void EmbeddedNetworkController::_request( json &memberTags = member["tags"]; json &dns = network["dns"]; - fprintf(stderr, "DNS Config for Network ID %.16llx: %s\n", nwid, OSUtils::jsonDump(dns, 2).c_str()); - if (metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV,0) <= 0) { // Old versions with no rules engine support get an allow everything rule. // Since rules are enforced bidirectionally, newer versions *will* still @@ -1714,11 +1712,9 @@ void EmbeddedNetworkController::_request( } if(dns.is_array()) { - fprintf(stderr, "dns is array of size %d\n", dns.size()); nc->dnsCount = 0; for(unsigned int p=0; p < dns.size(); ++p) { json &d = dns[p]; - fprintf(stderr, "%s\n", OSUtils::jsonDump(d, 2).c_str()); if (d.is_object()) { std::string domain = OSUtils::jsonString(d["domain"],""); memcpy(nc->dns[nc->dnsCount].domain, domain.c_str(), domain.size()); @@ -1733,7 +1729,6 @@ void EmbeddedNetworkController::_request( } } } else { - fprintf(stderr, "dns is NOT an array\n"); dns = json::array(); } diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 8463c13a8..81908c46b 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -456,7 +456,6 @@ void PostgreSQL::initializeNetworks(PGconn *conn) auto servers = json::array(); if (serverList.rfind("{",0) != std::string::npos) { serverList = serverList.substr(1, serverList.size()-2); - fprintf(stderr, "Server List: %s\n", serverList.c_str()); std::stringstream ss(serverList); while(ss.good()) { std::string server; @@ -468,7 +467,6 @@ void PostgreSQL::initializeNetworks(PGconn *conn) obj["servers"] = servers; config["dns"].push_back(obj); } - fprintf(stderr, "%s: %s\n", OSUtils::jsonString(config["nwid"], "").c_str(), OSUtils::jsonDump(config["dns"], 2).c_str()); PQclear(r2); From c0c215c83c75d76aad3aafa337d69d876c13f5cf Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 12 Aug 2020 13:08:47 -0700 Subject: [PATCH 126/362] single dns config per network --- controller/EmbeddedNetworkController.cpp | 25 +++---- controller/PostgreSQL.cpp | 87 ++++++++---------------- include/ZeroTierOne.h | 12 +--- node/DNS.hpp | 28 ++++---- node/Network.cpp | 6 +- node/NetworkConfig.cpp | 13 ++-- node/NetworkConfig.hpp | 4 +- osdep/MacDNSHelper.mm | 1 - service/OneService.cpp | 47 +++++-------- 9 files changed, 76 insertions(+), 147 deletions(-) diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index a505c3edb..ad42fabb1 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -1711,25 +1711,18 @@ void EmbeddedNetworkController::_request( } } - if(dns.is_array()) { - nc->dnsCount = 0; - for(unsigned int p=0; p < dns.size(); ++p) { - json &d = dns[p]; - if (d.is_object()) { - std::string domain = OSUtils::jsonString(d["domain"],""); - memcpy(nc->dns[nc->dnsCount].domain, domain.c_str(), domain.size()); - json &addrArray = d["servers"]; - if (addrArray.is_array()) { - for(unsigned int j = 0; j < addrArray.size() && j < ZT_MAX_DNS_SERVERS; ++j) { - json &addr = addrArray[j]; - nc->dns[nc->dnsCount].server_addr[j] = InetAddress(OSUtils::jsonString(addr,"").c_str()); - } - } - ++nc->dnsCount; + if(dns.is_object()) { + std::string domain = OSUtils::jsonString(dns["domain"],""); + memcpy(nc->dns.domain, domain.c_str(), domain.size()); + json &addrArray = dns["servers"]; + if (addrArray.is_array()) { + for(unsigned int j = 0; j < addrArray.size() && j < ZT_MAX_DNS_SERVERS; ++j) { + json &addr = addrArray[j]; + nc->dns.server_addr[j] = InetAddress(OSUtils::jsonString(addr,"").c_str()); } } } else { - dns = json::array(); + dns = json::object(); } // Issue a certificate of ownership for all static IPs diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 81908c46b..44ba2ae5d 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -447,12 +447,12 @@ void PostgreSQL::initializeNetworks(PGconn *conn) } n = PQntuples(r2); - config["dns"] = json::array(); - for (int j = 0; j < n; ++j) { - + if (n > 1) { + fprintf(stderr, "ERROR: invalid number of DNS configurations for network %s. Must be 0 or 1\n", nwid.c_str()); + } else if (n == 1) { json obj; - std::string domain = PQgetvalue(r2, j, 0); - std::string serverList = PQgetvalue(r2, j, 1); + std::string domain = PQgetvalue(r2, 0, 0); + std::string serverList = PQgetvalue(r2, 0, 1); auto servers = json::array(); if (serverList.rfind("{",0) != std::string::npos) { serverList = serverList.substr(1, serverList.size()-2); @@ -465,7 +465,7 @@ void PostgreSQL::initializeNetworks(PGconn *conn) } obj["domain"] = domain; obj["servers"] = servers; - config["dns"].push_back(obj); + config["dns"] = obj; } PQclear(r2); @@ -1461,67 +1461,38 @@ void PostgreSQL::commitThread() config = nullptr; continue; } + auto dns = (*config)["dns"]; + std::string domain = dns["domain"]; + std::stringstream servers; + servers << "{"; + for (auto j = dns["servers"].begin(); j < dns["servers"].end(); ++j) { + servers << *j; + if ( (j+1) != dns["servers"].end()) { + servers << ","; + } + } + servers << "}"; + const char *p[3] = { + id.c_str(), + domain.c_str(), + servers.str().c_str() + }; - res = PQexecParams(conn, - "DELETE FROM ztc_network_dns WHERE network_id = $1", - 1, + res = PQexecParams(conn, "INSERT INTO ztc_network_dns (network_id, domain, servers) VALUES ($1, $2, $3) ON CONFLICT (network_id) DO UPDATE SET domain = EXCLUDED.domain, servers = EXCLUDED.servers", + 3, NULL, - params, + p, NULL, NULL, 0); - if (PQresultStatus(res) != PGRES_COMMAND_OK) { - fprintf(stderr, "ERROR: Error updating dns: %s\n", PQresultErrorMessage(res)); + fprintf(stderr, "ERROR: Error updating DNS: %s\n", PQresultErrorMessage(res)); PQclear(res); - PQclear(PQexec(conn, "ROLLBACK")); - delete config; - config = nullptr; - continue; - } - - auto dns = (*config)["dns"]; - err = false; - for (auto i = dns.begin(); i < dns.end(); ++i) { - std::string domain = (*i)["domain"]; - std::stringstream servers; - servers << "{"; - for (auto j = dns["servers"].begin(); j < dns["servers"].end(); ++j) { - servers << *j; - if ( (j+1) != dns["servers"].end()) { - servers << ","; - } - } - servers << "}"; - - const char *p[3] = { - id.c_str(), - domain.c_str(), - servers.str().c_str() - }; - - res = PQexecParams(conn, "INSERT INTO ztc_network_dns (network_id, domain, servers) VALUES ($1, $2, $3)", - 3, - NULL, - p, - NULL, - NULL, - 0); - if (PQresultStatus(res) != PGRES_COMMAND_OK) { - fprintf(stderr, "ERROR: Error updating DNS: %s\n", PQresultErrorMessage(res)); - PQclear(res); - err = true; - break; - } - PQclear(res); - } - if (err) { - PQclear(PQexec(conn, "ROLLBACK")); - delete config; - config = nullptr; - continue; + err = true; + break; } + PQclear(res); res = PQexec(conn, "COMMIT"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index 82ae5edfd..f7e6ef6dc 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -125,11 +125,6 @@ extern "C" { */ #define ZT_MAX_NETWORK_ROUTES 32 -/** - * Maximum number of pushed DNS configurations on a network - */ -#define ZT_MAX_NETWORK_DNS 32 - /** * Maximum number of statically assigned IP addresses per network endpoint using ZT address management (not DHCP) */ @@ -1339,16 +1334,11 @@ typedef struct uint64_t mac; /* MAC in lower 48 bits */ uint32_t adi; /* Additional distinguishing information, usually zero except for IPv4 ARP groups */ } multicastSubscriptions[ZT_MAX_MULTICAST_SUBSCRIPTIONS]; - - /** - * Number of ZT-pushed DNS configuraitons - */ - unsigned int dnsCount; /** * Network specific DNS configuration */ - ZT_VirtualNetworkDNS dns[ZT_MAX_NETWORK_DNS]; + ZT_VirtualNetworkDNS dns; } ZT_VirtualNetworkConfig; /** diff --git a/node/DNS.hpp b/node/DNS.hpp index a770859d8..ceb81e33f 100644 --- a/node/DNS.hpp +++ b/node/DNS.hpp @@ -29,28 +29,24 @@ namespace ZeroTier { class DNS { public: template - static inline void serializeDNS(Buffer &b, const ZT_VirtualNetworkDNS *dns, unsigned int dnsCount) + static inline void serializeDNS(Buffer &b, const ZT_VirtualNetworkDNS *dns) { - for(unsigned int i = 0; i < dnsCount; ++i) { - b.append(dns[i].domain, 128); - for(unsigned int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { - InetAddress tmp(dns[i].server_addr[j]); - tmp.serialize(b); - } + b.append(dns->domain, 128); + for(unsigned int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { + InetAddress tmp(dns->server_addr[j]); + tmp.serialize(b); } } template - static inline void deserializeDNS(const Buffer &b, unsigned int &p, ZT_VirtualNetworkDNS *dns, const unsigned int dnsCount) + static inline void deserializeDNS(const Buffer &b, unsigned int &p, ZT_VirtualNetworkDNS *dns) { - memset(dns, 0, sizeof(ZT_VirtualNetworkDNS)*ZT_MAX_NETWORK_DNS); - for(unsigned int i = 0; i < dnsCount; ++i) { - char *d = (char*)b.data()+p; - memcpy(dns[i].domain, d, 128); - p += 128; - for (unsigned int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { - p += reinterpret_cast(&(dns[i].server_addr[j]))->deserialize(b, p); - } + char *d = (char*)b.data()+p; + memset(dns, 0, sizeof(ZT_VirtualNetworkDNS)); + memcpy(dns->domain, d, 128); + p += 128; + for (unsigned int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { + p += reinterpret_cast(&(dns->server_addr[j]))->deserialize(b, p); } } }; diff --git a/node/Network.cpp b/node/Network.cpp index 97642fa18..3fe489a29 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -1428,11 +1428,7 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const ec->multicastSubscriptions[i].adi = _myMulticastGroups[i].adi(); } - ec->dnsCount = _config.dnsCount; - fprintf(stderr, "Network::_externalConfig dnsCount: %d\n", ec->dnsCount); - if (ec->dnsCount > 0) { - memcpy(&ec->dns, &_config.dns, sizeof(ZT_VirtualNetworkDNS)); - } + memcpy(&ec->dns, &_config.dns, sizeof(ZT_VirtualNetworkDNS)); } void Network::_sendUpdatesToMembers(void *tPtr,const MulticastGroup *const newMulticastGroup) diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index 503d7400a..c793462d8 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -177,12 +177,9 @@ bool NetworkConfig::toDictionary(Dictionary &d,b } tmp->clear(); - if (dnsCount > 0) { - tmp->append(dnsCount); - DNS::serializeDNS(*tmp, dns, dnsCount); - if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_DNS,*tmp)) return false; - } + DNS::serializeDNS(*tmp, &dns); + if (tmp->size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_DNS,*tmp)) return false; } delete tmp; @@ -366,9 +363,7 @@ bool NetworkConfig::fromDictionary(const DictionarydnsCount = tmp->at(p); - p += sizeof(unsigned int); - DNS::deserializeDNS(*tmp, p, dns, (this->dnsCount <= ZT_MAX_NETWORK_DNS) ? this->dnsCount : ZT_MAX_NETWORK_DNS); + DNS::deserializeDNS(*tmp, p, &dns); } } diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp index 1daf98d01..6e66720e8 100644 --- a/node/NetworkConfig.hpp +++ b/node/NetworkConfig.hpp @@ -240,7 +240,7 @@ public: memset(routes, 0, sizeof(ZT_VirtualNetworkRoute)*ZT_MAX_NETWORK_ROUTES); memset(staticIps, 0, sizeof(InetAddress)*ZT_MAX_ZT_ASSIGNED_ADDRESSES); memset(rules, 0, sizeof(ZT_VirtualNetworkRule)*ZT_MAX_NETWORK_RULES); - memset(dns, 0, sizeof(ZT_VirtualNetworkDNS)*ZT_MAX_NETWORK_DNS); + memset(&dns, 0, sizeof(ZT_VirtualNetworkDNS)); } /** @@ -603,7 +603,7 @@ public: /** * ZT pushed DNS configuration */ - ZT_VirtualNetworkDNS dns[ZT_MAX_NETWORK_DNS]; + ZT_VirtualNetworkDNS dns; }; } // namespace ZeroTier diff --git a/osdep/MacDNSHelper.mm b/osdep/MacDNSHelper.mm index 1cfe1b265..c50de7915 100644 --- a/osdep/MacDNSHelper.mm +++ b/osdep/MacDNSHelper.mm @@ -34,7 +34,6 @@ void MacDNSHelper::setDNS(uint64_t nwid, const char *domain, const std::vectordnsCount;++i) { - nlohmann::json m; - m["domain"] = nc->dns[i].domain; - m["servers"] = nlohmann::json::array(); - for(int j=0;jdns[i].server_addr[j]); - if (a.isV4() || a.isV6()) { - char buf[256]; - m["servers"].push_back(a.toIpString(buf)); - } + nlohmann::json m; + m["domain"] = nc->dns.domain; + m["servers"] = nlohmann::json::array(); + for(int j=0;jdns.server_addr[j]); + if (a.isV4() || a.isV6()) { + char buf[256]; + m["servers"].push_back(a.toIpString(buf)); } } + nj["dns"] = m; + } static void _peerToJson(nlohmann::json &pj,const ZT_Peer *peer) @@ -2002,25 +2001,15 @@ public: } if (syncDns) { - char buf[128]; - if (n.config.dnsCount > ZT_MAX_NETWORK_DNS) { - fprintf(stderr, "ERROR: %d records > max %d. Skipping DNS\n", n.config.dnsCount, ZT_MAX_NETWORK_DNS); - return; - } - fprintf(stderr, "Syncing %d DNS configurations for network [%.16llx]\n", n.config.dnsCount, n.config.nwid); - for (int i = 0; i < n.config.dnsCount; ++i) { - if (strlen(n.config.dns[i].domain) != 0) { - fprintf(stderr, "Syncing DNS for domain: %s\n", n.config.dns[i].domain); - std::vector servers; - for (int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { - InetAddress a(n.config.dns[i].server_addr[j]); - if (a.isV4() || a.isV6()) { - fprintf(stderr, "\t Server %d: %s\n", j+1, a.toIpString(buf)); - servers.push_back(a); - } + if (strlen(n.config.dns.domain) != 0) { + std::vector servers; + for (int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { + InetAddress a(n.config.dns.server_addr[j]); + if (a.isV4() || a.isV6()) { + servers.push_back(a); } - n.tap->setDns(n.config.dns[i].domain, servers); } + n.tap->setDns(n.config.dns.domain, servers); } } } From 81c9db7a1548fa8eb132aaefc47367fbaa6a42d3 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 18 Aug 2020 11:46:29 -0700 Subject: [PATCH 127/362] fix libs for non-controller builds on macOS --- make-mac.mk | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/make-mac.mk b/make-mac.mk index acf17dd42..f44acabe3 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -28,12 +28,13 @@ include objects.mk ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o osdep/MacDNSHelper.o ext/http-parser/http_parser.o ifeq ($(ZT_CONTROLLER),1) - LIBS+=-L/usr/local/opt/libpq/lib -lpq ext/redis-plus-plus-1.1.1/install/macos/lib/libredis++.a ext/hiredis-0.14.1/lib/macos/libhiredis.a -framework SystemConfiguration -framework CoreFoundation + LIBS+=-L/usr/local/opt/libpq/lib -lpq ext/redis-plus-plus-1.1.1/install/macos/lib/libredis++.a ext/hiredis-0.14.1/lib/macos/libhiredis.a DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER_USE_REDIS -DZT_CONTROLLER INCLUDES+=-I/usr/local/opt/libpq/include -Iext/hiredis-0.14.1/include/ -Iext/redis-plus-plus-1.1.1/install/macos/include/sw/ - endif +LIBS+=-framework SystemConfiguration -framework CoreFoundation + # Official releases are signed with our Apple cert and apply software updates by default ifeq ($(ZT_OFFICIAL_RELEASE),1) DEFS+=-DZT_SOFTWARE_UPDATE_DEFAULT="\"apply\"" From 06730c7d1d64c43fe41d0a760e92a7cf461f37b0 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 20 Aug 2020 12:51:39 -0700 Subject: [PATCH 128/362] BSL date bump --- LICENSE.txt | 2 +- controller/DB.cpp | 2 +- controller/DB.hpp | 2 +- controller/DBMirrorSet.cpp | 2 +- controller/DBMirrorSet.hpp | 2 +- controller/EmbeddedNetworkController.cpp | 2 +- controller/EmbeddedNetworkController.hpp | 2 +- controller/FileDB.cpp | 2 +- controller/FileDB.hpp | 2 +- controller/LFDB.cpp | 2 +- controller/LFDB.hpp | 2 +- controller/PostgreSQL.cpp | 2 +- controller/PostgreSQL.hpp | 2 +- debian/copyright | 2 +- include/ZeroTierDebug.h | 2 +- include/ZeroTierOne.h | 2 +- node/Address.hpp | 2 +- node/AtomicCounter.hpp | 2 +- node/Bond.cpp | 2 +- node/Bond.hpp | 2 +- node/BondController.cpp | 2 +- node/BondController.hpp | 2 +- node/Buffer.hpp | 2 +- node/C25519.hpp | 2 +- node/Capability.cpp | 2 +- node/Capability.hpp | 2 +- node/CertificateOfMembership.cpp | 2 +- node/CertificateOfMembership.hpp | 2 +- node/CertificateOfOwnership.cpp | 2 +- node/CertificateOfOwnership.hpp | 2 +- node/Constants.hpp | 2 +- node/Credential.hpp | 2 +- node/DNS.hpp | 2 +- node/Dictionary.hpp | 2 +- node/Flow.hpp | 2 +- node/Hashtable.hpp | 2 +- node/Identity.cpp | 2 +- node/Identity.hpp | 2 +- node/IncomingPacket.cpp | 2 +- node/IncomingPacket.hpp | 2 +- node/InetAddress.cpp | 2 +- node/InetAddress.hpp | 2 +- node/MAC.hpp | 2 +- node/Membership.cpp | 2 +- node/Membership.hpp | 2 +- node/MulticastGroup.hpp | 2 +- node/Multicaster.cpp | 2 +- node/Multicaster.hpp | 2 +- node/Mutex.hpp | 2 +- node/Network.cpp | 2 +- node/Network.hpp | 2 +- node/NetworkConfig.cpp | 2 +- node/NetworkConfig.hpp | 2 +- node/NetworkController.hpp | 2 +- node/Node.cpp | 2 +- node/Node.hpp | 2 +- node/OutboundMulticast.cpp | 2 +- node/OutboundMulticast.hpp | 2 +- node/Packet.cpp | 2 +- node/Packet.hpp | 2 +- node/Path.cpp | 2 +- node/Path.hpp | 2 +- node/Peer.cpp | 2 +- node/Peer.hpp | 2 +- node/Poly1305.hpp | 2 +- node/Revocation.cpp | 2 +- node/Revocation.hpp | 2 +- node/RingBuffer.hpp | 2 +- node/RuntimeEnvironment.hpp | 2 +- node/SHA512.hpp | 2 +- node/SelfAwareness.cpp | 2 +- node/SelfAwareness.hpp | 2 +- node/SharedPtr.hpp | 2 +- node/Switch.cpp | 2 +- node/Switch.hpp | 2 +- node/Tag.cpp | 2 +- node/Tag.hpp | 2 +- node/Topology.cpp | 2 +- node/Topology.hpp | 2 +- node/Trace.cpp | 2 +- node/Trace.hpp | 2 +- node/Utils.cpp | 2 +- node/Utils.hpp | 2 +- node/World.hpp | 2 +- one.cpp | 2 +- osdep/Arp.cpp | 2 +- osdep/Arp.hpp | 2 +- osdep/BSDEthernetTap.cpp | 2 +- osdep/BSDEthernetTap.hpp | 2 +- osdep/Binder.hpp | 2 +- osdep/BlockingQueue.hpp | 2 +- osdep/EthernetTap.cpp | 2 +- osdep/EthernetTap.hpp | 2 +- osdep/Http.cpp | 2 +- osdep/Http.hpp | 2 +- osdep/Link.hpp | 2 +- osdep/LinuxEthernetTap.cpp | 2 +- osdep/LinuxEthernetTap.hpp | 2 +- osdep/LinuxNetLink.cpp | 2 +- osdep/LinuxNetLink.hpp | 2 +- osdep/MacEthernetTap.cpp | 2 +- osdep/MacEthernetTap.hpp | 2 +- osdep/MacEthernetTapAgent.c | 2 +- osdep/MacEthernetTapAgent.h | 2 +- osdep/MacKextEthernetTap.cpp | 2 +- osdep/MacKextEthernetTap.hpp | 2 +- osdep/ManagedRoute.cpp | 2 +- osdep/ManagedRoute.hpp | 2 +- osdep/NeighborDiscovery.cpp | 2 +- osdep/NeighborDiscovery.hpp | 2 +- osdep/NetBSDEthernetTap.cpp | 2 +- osdep/NetBSDEthernetTap.hpp | 2 +- osdep/OSUtils.cpp | 2 +- osdep/OSUtils.hpp | 2 +- osdep/Phy.hpp | 2 +- osdep/PortMapper.cpp | 2 +- osdep/PortMapper.hpp | 2 +- osdep/Thread.hpp | 2 +- osdep/WindowsEthernetTap.cpp | 2 +- osdep/WindowsEthernetTap.hpp | 2 +- rule-compiler/rule-compiler.js | 2 +- selftest.cpp | 2 +- service/OneService.cpp | 2 +- service/OneService.hpp | 2 +- service/SoftwareUpdater.cpp | 2 +- service/SoftwareUpdater.hpp | 2 +- version.h | 2 +- windows/ZeroTierOne/ZeroTierOneService.cpp | 2 +- windows/ZeroTierOne/ZeroTierOneService.h | 2 +- 129 files changed, 129 insertions(+), 129 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 78daf4c2a..13fa014a3 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -47,7 +47,7 @@ Additional Use Grant: You may make use of the Licensed Work, provided you services, social welfare, senior care, child care, and the care of persons with disabilities. -Change Date: 2023-01-01 +Change Date: 2025-01-01 Change License: Apache License version 2.0 as published by the Apache Software Foundation diff --git a/controller/DB.cpp b/controller/DB.cpp index 775daf1a5..8a86ae376 100644 --- a/controller/DB.cpp +++ b/controller/DB.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/controller/DB.hpp b/controller/DB.hpp index 8a3c05e0b..6a6906eff 100644 --- a/controller/DB.hpp +++ b/controller/DB.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/controller/DBMirrorSet.cpp b/controller/DBMirrorSet.cpp index b2c7c71b1..f19741bb3 100644 --- a/controller/DBMirrorSet.cpp +++ b/controller/DBMirrorSet.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/controller/DBMirrorSet.hpp b/controller/DBMirrorSet.hpp index 6ca6c452b..967cd9360 100644 --- a/controller/DBMirrorSet.hpp +++ b/controller/DBMirrorSet.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index ad42fabb1..3fb39aa3a 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index 3fb421074..e499dd647 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/controller/FileDB.cpp b/controller/FileDB.cpp index b4eaf58c7..bf573f3bf 100644 --- a/controller/FileDB.cpp +++ b/controller/FileDB.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/controller/FileDB.hpp b/controller/FileDB.hpp index fcd7af0f7..545d26e3d 100644 --- a/controller/FileDB.hpp +++ b/controller/FileDB.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/controller/LFDB.cpp b/controller/LFDB.cpp index d11b77a07..ac029cc27 100644 --- a/controller/LFDB.cpp +++ b/controller/LFDB.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/controller/LFDB.hpp b/controller/LFDB.hpp index 0849ae571..9f654bfb7 100644 --- a/controller/LFDB.hpp +++ b/controller/LFDB.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 44ba2ae5d..4dd63a0a7 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index f61670132..c1d9dfd1a 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/debian/copyright b/debian/copyright index 493e6a27b..d22affa29 100644 --- a/debian/copyright +++ b/debian/copyright @@ -12,7 +12,7 @@ License: ZeroTier BSL 1.1 Use of this software is governed by the Business Source License included in the LICENSE.TXT file in the project's root directory. - Change Date: 2023-01-01 + Change Date: 2025-01-01 On the date above, in accordance with the Business Source License, use of this software will be governed by version 2.0 of the Apache License. diff --git a/include/ZeroTierDebug.h b/include/ZeroTierDebug.h index aa7ab4fe2..27f654935 100644 --- a/include/ZeroTierDebug.h +++ b/include/ZeroTierDebug.h @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index f7e6ef6dc..83c4a4787 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Address.hpp b/node/Address.hpp index 0749adbfa..d72eb6af6 100644 --- a/node/Address.hpp +++ b/node/Address.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/AtomicCounter.hpp b/node/AtomicCounter.hpp index ea84259b6..cbfc57843 100644 --- a/node/AtomicCounter.hpp +++ b/node/AtomicCounter.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Bond.cpp b/node/Bond.cpp index 4e555dbd2..70d0e5c5a 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Bond.hpp b/node/Bond.hpp index c8a89b616..abb78f6c4 100644 --- a/node/Bond.hpp +++ b/node/Bond.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/BondController.cpp b/node/BondController.cpp index 066c10442..7c97ef7a4 100644 --- a/node/BondController.cpp +++ b/node/BondController.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/BondController.hpp b/node/BondController.hpp index 2e0c15072..892da2a43 100644 --- a/node/BondController.hpp +++ b/node/BondController.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Buffer.hpp b/node/Buffer.hpp index d0e152b94..a8ca82ed8 100644 --- a/node/Buffer.hpp +++ b/node/Buffer.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/C25519.hpp b/node/C25519.hpp index db34f621f..d9a15f84b 100644 --- a/node/C25519.hpp +++ b/node/C25519.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Capability.cpp b/node/Capability.cpp index ad1b0f45e..21e7144f8 100644 --- a/node/Capability.cpp +++ b/node/Capability.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Capability.hpp b/node/Capability.hpp index 12730028b..115973d50 100644 --- a/node/Capability.hpp +++ b/node/Capability.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/CertificateOfMembership.cpp b/node/CertificateOfMembership.cpp index 20ae229eb..10cb0863a 100644 --- a/node/CertificateOfMembership.cpp +++ b/node/CertificateOfMembership.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/CertificateOfMembership.hpp b/node/CertificateOfMembership.hpp index 5043f4e62..f8500628d 100644 --- a/node/CertificateOfMembership.hpp +++ b/node/CertificateOfMembership.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/CertificateOfOwnership.cpp b/node/CertificateOfOwnership.cpp index d6cd7d71e..70be0a044 100644 --- a/node/CertificateOfOwnership.cpp +++ b/node/CertificateOfOwnership.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/CertificateOfOwnership.hpp b/node/CertificateOfOwnership.hpp index 17c843bb0..abd62df1a 100644 --- a/node/CertificateOfOwnership.hpp +++ b/node/CertificateOfOwnership.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Constants.hpp b/node/Constants.hpp index b8437481b..3f971ce4d 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Credential.hpp b/node/Credential.hpp index 5b58da2b5..fcc7773bb 100644 --- a/node/Credential.hpp +++ b/node/Credential.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/DNS.hpp b/node/DNS.hpp index ceb81e33f..b36d960d4 100644 --- a/node/DNS.hpp +++ b/node/DNS.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp index 5374b9e60..7a4572427 100644 --- a/node/Dictionary.hpp +++ b/node/Dictionary.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Flow.hpp b/node/Flow.hpp index b19fd475c..77a4b207f 100644 --- a/node/Flow.hpp +++ b/node/Flow.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Hashtable.hpp b/node/Hashtable.hpp index 9a0f3e208..5a38f29cf 100644 --- a/node/Hashtable.hpp +++ b/node/Hashtable.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Identity.cpp b/node/Identity.cpp index 4e92765f5..79ec31453 100644 --- a/node/Identity.cpp +++ b/node/Identity.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Identity.hpp b/node/Identity.hpp index 28d1f0043..a754b6727 100644 --- a/node/Identity.hpp +++ b/node/Identity.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 43e36f3ce..94f7f6301 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/IncomingPacket.hpp b/node/IncomingPacket.hpp index b1032d99d..134b5b3d0 100644 --- a/node/IncomingPacket.hpp +++ b/node/IncomingPacket.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/InetAddress.cpp b/node/InetAddress.cpp index 3eee1d4b8..bca97ad2f 100644 --- a/node/InetAddress.cpp +++ b/node/InetAddress.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp index e16ae43b3..67f70d2a9 100644 --- a/node/InetAddress.hpp +++ b/node/InetAddress.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/MAC.hpp b/node/MAC.hpp index 3f7b00606..76ee4bf31 100644 --- a/node/MAC.hpp +++ b/node/MAC.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Membership.cpp b/node/Membership.cpp index 100946b2e..4f829ecb5 100644 --- a/node/Membership.cpp +++ b/node/Membership.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Membership.hpp b/node/Membership.hpp index caff957b7..476987714 100644 --- a/node/Membership.hpp +++ b/node/Membership.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/MulticastGroup.hpp b/node/MulticastGroup.hpp index a38b284de..2a68d0ab9 100644 --- a/node/MulticastGroup.hpp +++ b/node/MulticastGroup.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp index 82babae00..6b9bdd374 100644 --- a/node/Multicaster.cpp +++ b/node/Multicaster.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Multicaster.hpp b/node/Multicaster.hpp index f4c80108d..55c09eb3b 100644 --- a/node/Multicaster.hpp +++ b/node/Multicaster.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Mutex.hpp b/node/Mutex.hpp index 14b6c81f9..0cbe27bd8 100644 --- a/node/Mutex.hpp +++ b/node/Mutex.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Network.cpp b/node/Network.cpp index 3fe489a29..893bd90ed 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Network.hpp b/node/Network.hpp index 701a46111..b20d8b66b 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index c793462d8..5259a3e32 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp index 6e66720e8..06e0127fe 100644 --- a/node/NetworkConfig.hpp +++ b/node/NetworkConfig.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/NetworkController.hpp b/node/NetworkController.hpp index b344787b8..29a2d8f17 100644 --- a/node/NetworkController.hpp +++ b/node/NetworkController.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Node.cpp b/node/Node.cpp index e4b0a0735..05f5a247b 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Node.hpp b/node/Node.hpp index 6461e4cd6..2bbd3b47f 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/OutboundMulticast.cpp b/node/OutboundMulticast.cpp index 1f2090ec8..2d05c49e1 100644 --- a/node/OutboundMulticast.cpp +++ b/node/OutboundMulticast.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/OutboundMulticast.hpp b/node/OutboundMulticast.hpp index a40dc80ff..023f798e3 100644 --- a/node/OutboundMulticast.hpp +++ b/node/OutboundMulticast.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Packet.cpp b/node/Packet.cpp index 381864a45..395c0e192 100644 --- a/node/Packet.cpp +++ b/node/Packet.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Packet.hpp b/node/Packet.hpp index f1112403e..1ffca5a5c 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Path.cpp b/node/Path.cpp index e209e9c04..f655e3730 100644 --- a/node/Path.cpp +++ b/node/Path.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Path.hpp b/node/Path.hpp index 5a3a1ef82..caa6bbaaa 100644 --- a/node/Path.hpp +++ b/node/Path.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Peer.cpp b/node/Peer.cpp index 4936abe6d..bf39c7d14 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Peer.hpp b/node/Peer.hpp index 0f08bed32..80a2264e3 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Poly1305.hpp b/node/Poly1305.hpp index 936c7b4ad..36425e341 100644 --- a/node/Poly1305.hpp +++ b/node/Poly1305.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Revocation.cpp b/node/Revocation.cpp index 6cc6c99dd..f196b43c7 100644 --- a/node/Revocation.cpp +++ b/node/Revocation.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Revocation.hpp b/node/Revocation.hpp index 7fda5c668..d27e7180c 100644 --- a/node/Revocation.hpp +++ b/node/Revocation.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/RingBuffer.hpp b/node/RingBuffer.hpp index 42047a873..e2b90ce5c 100644 --- a/node/RingBuffer.hpp +++ b/node/RingBuffer.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/RuntimeEnvironment.hpp b/node/RuntimeEnvironment.hpp index 2e860b6ab..4603afa0f 100644 --- a/node/RuntimeEnvironment.hpp +++ b/node/RuntimeEnvironment.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/SHA512.hpp b/node/SHA512.hpp index 676f35d50..eefaead16 100644 --- a/node/SHA512.hpp +++ b/node/SHA512.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/SelfAwareness.cpp b/node/SelfAwareness.cpp index f4684d82f..57b409c12 100644 --- a/node/SelfAwareness.cpp +++ b/node/SelfAwareness.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/SelfAwareness.hpp b/node/SelfAwareness.hpp index 0b7774b62..f34d5778e 100644 --- a/node/SelfAwareness.hpp +++ b/node/SelfAwareness.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/SharedPtr.hpp b/node/SharedPtr.hpp index 84b1d7b5d..dff0fa4e9 100644 --- a/node/SharedPtr.hpp +++ b/node/SharedPtr.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Switch.cpp b/node/Switch.cpp index 4596dd276..c24e2d231 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Switch.hpp b/node/Switch.hpp index f1436c7cf..4ca1c0e50 100644 --- a/node/Switch.hpp +++ b/node/Switch.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Tag.cpp b/node/Tag.cpp index 69a17a283..78f5d1f3c 100644 --- a/node/Tag.cpp +++ b/node/Tag.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Tag.hpp b/node/Tag.hpp index ada31b8fd..cad8a5c31 100644 --- a/node/Tag.hpp +++ b/node/Tag.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Topology.cpp b/node/Topology.cpp index 01a81fccc..9933f35a2 100644 --- a/node/Topology.cpp +++ b/node/Topology.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Topology.hpp b/node/Topology.hpp index 56d4591c2..2d4e5d92c 100644 --- a/node/Topology.hpp +++ b/node/Topology.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Trace.cpp b/node/Trace.cpp index 10172fc4c..05022e95b 100644 --- a/node/Trace.cpp +++ b/node/Trace.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Trace.hpp b/node/Trace.hpp index f2453bcc7..f06b4001b 100644 --- a/node/Trace.hpp +++ b/node/Trace.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Utils.cpp b/node/Utils.cpp index e714967a8..10da66767 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Utils.hpp b/node/Utils.hpp index b80a7528d..947f5b3e0 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/World.hpp b/node/World.hpp index 3921f3803..a8b80e0ca 100644 --- a/node/World.hpp +++ b/node/World.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/one.cpp b/one.cpp index 8614f6341..20cdebfed 100644 --- a/one.cpp +++ b/one.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/Arp.cpp b/osdep/Arp.cpp index 43f7868d6..1f25a44d5 100644 --- a/osdep/Arp.cpp +++ b/osdep/Arp.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/Arp.hpp b/osdep/Arp.hpp index 93a8450bc..dc777fdb6 100644 --- a/osdep/Arp.hpp +++ b/osdep/Arp.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/BSDEthernetTap.cpp b/osdep/BSDEthernetTap.cpp index ae4f2165f..946cf9fc5 100644 --- a/osdep/BSDEthernetTap.cpp +++ b/osdep/BSDEthernetTap.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/BSDEthernetTap.hpp b/osdep/BSDEthernetTap.hpp index 134168176..c66fb6f79 100644 --- a/osdep/BSDEthernetTap.hpp +++ b/osdep/BSDEthernetTap.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/Binder.hpp b/osdep/Binder.hpp index 8076b6e92..3a12ba4d2 100644 --- a/osdep/Binder.hpp +++ b/osdep/Binder.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/BlockingQueue.hpp b/osdep/BlockingQueue.hpp index 0a3172884..c99eba503 100644 --- a/osdep/BlockingQueue.hpp +++ b/osdep/BlockingQueue.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/EthernetTap.cpp b/osdep/EthernetTap.cpp index 10fe6d895..f30a96e60 100644 --- a/osdep/EthernetTap.cpp +++ b/osdep/EthernetTap.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/EthernetTap.hpp b/osdep/EthernetTap.hpp index d8de16886..bf42526ac 100644 --- a/osdep/EthernetTap.hpp +++ b/osdep/EthernetTap.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/Http.cpp b/osdep/Http.cpp index 9ff1a0689..173e71985 100644 --- a/osdep/Http.cpp +++ b/osdep/Http.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/Http.hpp b/osdep/Http.hpp index d9cad4dc1..684215640 100644 --- a/osdep/Http.hpp +++ b/osdep/Http.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/Link.hpp b/osdep/Link.hpp index 6cbbbdfbe..88e24ecaf 100644 --- a/osdep/Link.hpp +++ b/osdep/Link.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/LinuxEthernetTap.cpp b/osdep/LinuxEthernetTap.cpp index b340561ed..1d6bc430a 100644 --- a/osdep/LinuxEthernetTap.cpp +++ b/osdep/LinuxEthernetTap.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/LinuxEthernetTap.hpp b/osdep/LinuxEthernetTap.hpp index 58c75f709..0cef1cb9f 100644 --- a/osdep/LinuxEthernetTap.hpp +++ b/osdep/LinuxEthernetTap.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/LinuxNetLink.cpp b/osdep/LinuxNetLink.cpp index 13e7176e4..4c6d21a87 100644 --- a/osdep/LinuxNetLink.cpp +++ b/osdep/LinuxNetLink.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/LinuxNetLink.hpp b/osdep/LinuxNetLink.hpp index 01bde04b6..73c017736 100644 --- a/osdep/LinuxNetLink.hpp +++ b/osdep/LinuxNetLink.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/MacEthernetTap.cpp b/osdep/MacEthernetTap.cpp index 4095e1331..bc5a6710d 100644 --- a/osdep/MacEthernetTap.cpp +++ b/osdep/MacEthernetTap.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/MacEthernetTap.hpp b/osdep/MacEthernetTap.hpp index 7945bb408..c9f9a3b22 100644 --- a/osdep/MacEthernetTap.hpp +++ b/osdep/MacEthernetTap.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/MacEthernetTapAgent.c b/osdep/MacEthernetTapAgent.c index a58a8d706..361018e28 100644 --- a/osdep/MacEthernetTapAgent.c +++ b/osdep/MacEthernetTapAgent.c @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/MacEthernetTapAgent.h b/osdep/MacEthernetTapAgent.h index 2c891c360..d5f24ccda 100644 --- a/osdep/MacEthernetTapAgent.h +++ b/osdep/MacEthernetTapAgent.h @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/MacKextEthernetTap.cpp b/osdep/MacKextEthernetTap.cpp index ea26b53c1..836ac0360 100644 --- a/osdep/MacKextEthernetTap.cpp +++ b/osdep/MacKextEthernetTap.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/MacKextEthernetTap.hpp b/osdep/MacKextEthernetTap.hpp index d7ea53028..4c61c2843 100644 --- a/osdep/MacKextEthernetTap.hpp +++ b/osdep/MacKextEthernetTap.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/ManagedRoute.cpp b/osdep/ManagedRoute.cpp index a5dc5b3cc..3094c32d1 100644 --- a/osdep/ManagedRoute.cpp +++ b/osdep/ManagedRoute.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/ManagedRoute.hpp b/osdep/ManagedRoute.hpp index d1f60d3f0..9cdf3f264 100644 --- a/osdep/ManagedRoute.hpp +++ b/osdep/ManagedRoute.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/NeighborDiscovery.cpp b/osdep/NeighborDiscovery.cpp index 21c02b48f..d5a0219ba 100644 --- a/osdep/NeighborDiscovery.cpp +++ b/osdep/NeighborDiscovery.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/NeighborDiscovery.hpp b/osdep/NeighborDiscovery.hpp index 5abf3129b..d8b32ac94 100644 --- a/osdep/NeighborDiscovery.hpp +++ b/osdep/NeighborDiscovery.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/NetBSDEthernetTap.cpp b/osdep/NetBSDEthernetTap.cpp index 1c0f017cb..7b3c101f9 100644 --- a/osdep/NetBSDEthernetTap.cpp +++ b/osdep/NetBSDEthernetTap.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/NetBSDEthernetTap.hpp b/osdep/NetBSDEthernetTap.hpp index 9c00c0e25..c25e5a165 100644 --- a/osdep/NetBSDEthernetTap.hpp +++ b/osdep/NetBSDEthernetTap.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp index 537e14966..7af1a1b7b 100644 --- a/osdep/OSUtils.cpp +++ b/osdep/OSUtils.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/OSUtils.hpp b/osdep/OSUtils.hpp index aa78bad4e..1fa4c4ab0 100644 --- a/osdep/OSUtils.hpp +++ b/osdep/OSUtils.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/Phy.hpp b/osdep/Phy.hpp index 30da8b395..8eb184ba1 100644 --- a/osdep/Phy.hpp +++ b/osdep/Phy.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/PortMapper.cpp b/osdep/PortMapper.cpp index caad3ed42..14bfe23af 100644 --- a/osdep/PortMapper.cpp +++ b/osdep/PortMapper.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/PortMapper.hpp b/osdep/PortMapper.hpp index be2c64682..c05021346 100644 --- a/osdep/PortMapper.hpp +++ b/osdep/PortMapper.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/Thread.hpp b/osdep/Thread.hpp index 4cd569941..1b0c354a9 100644 --- a/osdep/Thread.hpp +++ b/osdep/Thread.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp index 17aa99116..596fec340 100644 --- a/osdep/WindowsEthernetTap.cpp +++ b/osdep/WindowsEthernetTap.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/osdep/WindowsEthernetTap.hpp b/osdep/WindowsEthernetTap.hpp index c03c1e414..243d16340 100644 --- a/osdep/WindowsEthernetTap.hpp +++ b/osdep/WindowsEthernetTap.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/rule-compiler/rule-compiler.js b/rule-compiler/rule-compiler.js index f9b3aafaa..d7771169c 100644 --- a/rule-compiler/rule-compiler.js +++ b/rule-compiler/rule-compiler.js @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/selftest.cpp b/selftest.cpp index 115bc4cb4..73281e6a1 100644 --- a/selftest.cpp +++ b/selftest.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/service/OneService.cpp b/service/OneService.cpp index 076dbb59d..140a66103 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2024-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/service/OneService.hpp b/service/OneService.hpp index 53bbe2b81..0b10770a5 100644 --- a/service/OneService.hpp +++ b/service/OneService.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/service/SoftwareUpdater.cpp b/service/SoftwareUpdater.cpp index 5800f860f..6ace5fd84 100644 --- a/service/SoftwareUpdater.cpp +++ b/service/SoftwareUpdater.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/service/SoftwareUpdater.hpp b/service/SoftwareUpdater.hpp index 24a2bc725..46ceeb42b 100644 --- a/service/SoftwareUpdater.hpp +++ b/service/SoftwareUpdater.hpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/version.h b/version.h index 2e427cc8d..ccfe7d2a1 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/windows/ZeroTierOne/ZeroTierOneService.cpp b/windows/ZeroTierOne/ZeroTierOneService.cpp index 7e4cbf028..bb2b2e631 100644 --- a/windows/ZeroTierOne/ZeroTierOneService.cpp +++ b/windows/ZeroTierOne/ZeroTierOneService.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/windows/ZeroTierOne/ZeroTierOneService.h b/windows/ZeroTierOne/ZeroTierOneService.h index c4edb8209..ac9124856 100644 --- a/windows/ZeroTierOne/ZeroTierOneService.h +++ b/windows/ZeroTierOne/ZeroTierOneService.h @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. From 3fd8efe6423ca6c0e089bc14e090dd7d2eccca32 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 21 Aug 2020 09:56:53 -0700 Subject: [PATCH 129/362] AES builds now --- make-linux.mk | 2 + make-mac.mk | 5 +- node/AES.cpp | 1669 ++++++++++++++++++++++++++++++++++++++++++++ node/AES.hpp | 580 +++++++++++++++ node/Constants.hpp | 15 + node/Utils.cpp | 79 +++ node/Utils.hpp | 444 ++++++++++-- objects.mk | 1 + 8 files changed, 2732 insertions(+), 63 deletions(-) create mode 100644 node/AES.cpp create mode 100644 node/AES.hpp diff --git a/make-linux.mk b/make-linux.mk index 30ccad4f2..6db07bada 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -125,11 +125,13 @@ ifeq ($(CC_MACH),x86_64) ZT_ARCHITECTURE=2 ZT_USE_X64_ASM_SALSA=1 ZT_USE_X64_ASM_ED25519=1 + override CFLAGS+=-msse -msse2 -mssse3 -msse4 -msse4.1 -maes -mpclmul endif ifeq ($(CC_MACH),amd64) ZT_ARCHITECTURE=2 ZT_USE_X64_ASM_SALSA=1 ZT_USE_X64_ASM_ED25519=1 + override CFLAGS+=-msse -msse2 -mssse3 -msse4 -msse4.1 -maes -mpclmul endif ifeq ($(CC_MACH),powerpc64le) ZT_ARCHITECTURE=8 diff --git a/make-mac.mk b/make-mac.mk index f44acabe3..c608e037d 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -1,10 +1,9 @@ - CC=clang CXX=clang++ INCLUDES= DEFS= LIBS= -ARCH_FLAGS= +ARCH_FLAGS=-msse -msse2 -mssse3 -msse4 -msse4.1 -maes -mpclmul CODESIGN=echo PRODUCTSIGN=echo CODESIGN_APP_CERT= @@ -67,7 +66,7 @@ endif # Debug mode -- dump trace output, build binary with -g ifeq ($(ZT_DEBUG),1) ZT_TRACE=1 - CFLAGS+=-Wall -g $(INCLUDES) $(DEFS) + CFLAGS+=-Wall -g $(INCLUDES) $(DEFS) $(ARCH_FLAGS) STRIP=echo # The following line enables optimization for the crypto code, since # C25519 in particular is almost UNUSABLE in heavy testing without it. diff --git a/node/AES.cpp b/node/AES.cpp new file mode 100644 index 000000000..87ca39c83 --- /dev/null +++ b/node/AES.cpp @@ -0,0 +1,1669 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2025-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +#include "Constants.hpp" +#include "AES.hpp" + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + +#define Te1_r(x) ZT_ROR32(Te0[x], 8U) +#define Te2_r(x) ZT_ROR32(Te0[x], 16U) +#define Te3_r(x) ZT_ROR32(Te0[x], 24U) +#define Td1_r(x) ZT_ROR32(Td0[x], 8U) +#define Td2_r(x) ZT_ROR32(Td0[x], 16U) +#define Td3_r(x) ZT_ROR32(Td0[x], 24U) + +namespace ZeroTier { + +// GMAC --------------------------------------------------------------------------------------------------------------- + +namespace { + +#ifdef ZT_AES_NEON + +ZT_INLINE uint8x16_t s_clmul_armneon_crypto(uint8x16_t h, uint8x16_t y, const uint8_t b[16]) noexcept +{ + uint8x16_t r0, r1, t0, t1; + r0 = vld1q_u8(b); + const uint8x16_t z = veorq_u8(h, h); + y = veorq_u8(r0, y); + y = vrbitq_u8(y); + const uint8x16_t p = vreinterpretq_u8_u64(vdupq_n_u64(0x0000000000000087)); + t0 = vextq_u8(y, y, 8); + __asm__ __volatile__("pmull %0.1q, %1.1d, %2.1d \n\t" : "=w" (r0) : "w" (h), "w" (y)); + __asm__ __volatile__("pmull2 %0.1q, %1.2d, %2.2d \n\t" :"=w" (r1) : "w" (h), "w" (y)); + __asm__ __volatile__("pmull %0.1q, %1.1d, %2.1d \n\t" : "=w" (t1) : "w" (h), "w" (t0)); + __asm__ __volatile__("pmull2 %0.1q, %1.2d, %2.2d \n\t" :"=w" (t0) : "w" (h), "w" (t0)); + t0 = veorq_u8(t0, t1); + t1 = vextq_u8(z, t0, 8); + r0 = veorq_u8(r0, t1); + t1 = vextq_u8(t0, z, 8); + r1 = veorq_u8(r1, t1); + __asm__ __volatile__("pmull2 %0.1q, %1.2d, %2.2d \n\t" :"=w" (t0) : "w" (r1), "w" (p)); + t1 = vextq_u8(t0, z, 8); + r1 = veorq_u8(r1, t1); + t1 = vextq_u8(z, t0, 8); + r0 = veorq_u8(r0, t1); + __asm__ __volatile__("pmull %0.1q, %1.1d, %2.1d \n\t" : "=w" (t0) : "w" (r1), "w" (p)); + return vrbitq_u8(veorq_u8(r0, t0)); +} + +#endif // ZT_AES_NEON + +#define s_bmul32(N, x, y, rh, rl) \ + uint32_t x0t_##N = (x) & 0x11111111U; \ + uint32_t x1t_##N = (x) & 0x22222222U; \ + uint32_t x2t_##N = (x) & 0x44444444U; \ + uint32_t x3t_##N = (x) & 0x88888888U; \ + uint32_t y0t_##N = (y) & 0x11111111U; \ + uint32_t y1t_##N = (y) & 0x22222222U; \ + uint32_t y2t_##N = (y) & 0x44444444U; \ + uint32_t y3t_##N = (y) & 0x88888888U; \ + uint64_t z0t_##N = (((uint64_t)x0t_##N * y0t_##N) ^ ((uint64_t)x1t_##N * y3t_##N) ^ ((uint64_t)x2t_##N * y2t_##N) ^ ((uint64_t)x3t_##N * y1t_##N)) & 0x1111111111111111ULL; \ + uint64_t z1t_##N = (((uint64_t)x0t_##N * y1t_##N) ^ ((uint64_t)x1t_##N * y0t_##N) ^ ((uint64_t)x2t_##N * y3t_##N) ^ ((uint64_t)x3t_##N * y2t_##N)) & 0x2222222222222222ULL; \ + uint64_t z2t_##N = (((uint64_t)x0t_##N * y2t_##N) ^ ((uint64_t)x1t_##N * y1t_##N) ^ ((uint64_t)x2t_##N * y0t_##N) ^ ((uint64_t)x3t_##N * y3t_##N)) & 0x4444444444444444ULL; \ + z0t_##N |= z1t_##N; \ + z2t_##N |= z0t_##N; \ + uint64_t zt_##N = z2t_##N | ((((uint64_t)x0t_##N * y3t_##N) ^ ((uint64_t)x1t_##N * y2t_##N) ^ ((uint64_t)x2t_##N * y1t_##N) ^ ((uint64_t)x3t_##N * y0t_##N)) & 0x8888888888888888ULL); \ + (rh) = (uint32_t)(zt_##N >> 32U); \ + (rl) = (uint32_t)zt_##N; + +void s_gfmul(const uint64_t hh, const uint64_t hl, uint64_t &y0, uint64_t &y1) noexcept +{ + uint32_t hhh = (uint32_t)(hh >> 32U); + uint32_t hhl = (uint32_t)hh; + uint32_t hlh = (uint32_t)(hl >> 32U); + uint32_t hll = (uint32_t)hl; + uint32_t hhXlh = hhh ^hlh; + uint32_t hhXll = hhl ^hll; + uint64_t yl = Utils::ntoh(y0); + uint64_t yh = Utils::ntoh(y1); + uint32_t cilh = (uint32_t)(yh >> 32U); + uint32_t cill = (uint32_t)yh; + uint32_t cihh = (uint32_t)(yl >> 32U); + uint32_t cihl = (uint32_t)yl; + uint32_t cihXlh = cihh ^cilh; + uint32_t cihXll = cihl ^cill; + uint32_t aah, aal, abh, abl, ach, acl; + s_bmul32(M0, cihh, hhh, aah, aal); + s_bmul32(M1, cihl, hhl, abh, abl); + s_bmul32(M2, cihh ^ cihl, hhh ^ hhl, ach, acl); + ach ^= aah ^ abh; + acl ^= aal ^ abl; + aal ^= ach; + abh ^= acl; + uint32_t bah, bal, bbh, bbl, bch, bcl; + s_bmul32(M3, cilh, hlh, bah, bal); + s_bmul32(M4, cill, hll, bbh, bbl); + s_bmul32(M5, cilh ^ cill, hlh ^ hll, bch, bcl); + bch ^= bah ^ bbh; + bcl ^= bal ^ bbl; + bal ^= bch; + bbh ^= bcl; + uint32_t cah, cal, cbh, cbl, cch, ccl; + s_bmul32(M6, cihXlh, hhXlh, cah, cal); + s_bmul32(M7, cihXll, hhXll, cbh, cbl); + s_bmul32(M8, cihXlh ^ cihXll, hhXlh ^ hhXll, cch, ccl); + cch ^= cah ^ cbh; + ccl ^= cal ^ cbl; + cal ^= cch; + cbh ^= ccl; + cah ^= bah ^ aah; + cal ^= bal ^ aal; + cbh ^= bbh ^ abh; + cbl ^= bbl ^ abl; + uint64_t zhh = ((uint64_t)aah << 32U) | aal; + uint64_t zhl = (((uint64_t)abh << 32U) | abl) ^(((uint64_t)cah << 32U) | cal); + uint64_t zlh = (((uint64_t)bah << 32U) | bal) ^(((uint64_t)cbh << 32U) | cbl); + uint64_t zll = ((uint64_t)bbh << 32U) | bbl; + zhh = zhh << 1U | zhl >> 63U; + zhl = zhl << 1U | zlh >> 63U; + zlh = zlh << 1U | zll >> 63U; + zll <<= 1U; + zlh ^= (zll << 63U) ^ (zll << 62U) ^ (zll << 57U); + zhh ^= zlh ^ (zlh >> 1U) ^ (zlh >> 2U) ^ (zlh >> 7U); + zhl ^= zll ^ (zll >> 1U) ^ (zll >> 2U) ^ (zll >> 7U) ^ (zlh << 63U) ^ (zlh << 62U) ^ (zlh << 57U); + y0 = Utils::hton(zhh); + y1 = Utils::hton(zhl); +} + +} // anonymous namespace + +#ifdef ZT_AES_AESNI + +// SSE shuffle parameter to reverse bytes in a 128-bit vector. +static const __m128i s_sseSwapBytes = _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + +static __m128i p_gmacPCLMUL128(const __m128i h, __m128i y) noexcept +{ + y = _mm_shuffle_epi8(y, s_sseSwapBytes); + __m128i t1 = _mm_clmulepi64_si128(h, y, 0x00); + __m128i t2 = _mm_clmulepi64_si128(h, y, 0x01); + __m128i t3 = _mm_clmulepi64_si128(h, y, 0x10); + __m128i t4 = _mm_clmulepi64_si128(h, y, 0x11); + t2 = _mm_xor_si128(t2, t3); + t3 = _mm_slli_si128(t2, 8); + t2 = _mm_srli_si128(t2, 8); + t1 = _mm_xor_si128(t1, t3); + t4 = _mm_xor_si128(t4, t2); + __m128i t5 = _mm_srli_epi32(t1, 31); + t1 = _mm_or_si128(_mm_slli_epi32(t1, 1), _mm_slli_si128(t5, 4)); + t4 = _mm_or_si128(_mm_or_si128(_mm_slli_epi32(t4, 1), _mm_slli_si128(_mm_srli_epi32(t4, 31), 4)), _mm_srli_si128(t5, 12)); + t5 = _mm_xor_si128(_mm_xor_si128(_mm_slli_epi32(t1, 31), _mm_slli_epi32(t1, 30)), _mm_slli_epi32(t1, 25)); + t1 = _mm_xor_si128(t1, _mm_slli_si128(t5, 12)); + t4 = _mm_xor_si128(_mm_xor_si128(_mm_xor_si128(_mm_xor_si128(_mm_xor_si128(t4, _mm_srli_si128(t5, 4)), t1), _mm_srli_epi32(t1, 2)), _mm_srli_epi32(t1, 7)), _mm_srli_epi32(t1, 1)); + return _mm_shuffle_epi8(t4, s_sseSwapBytes); +} + +#endif + +void AES::GMAC::update(const void *const data, unsigned int len) noexcept +{ + const uint8_t *in = reinterpret_cast(data); + _len += len; + +#ifdef ZT_AES_AESNI + if (likely(Utils::CPUID.aes)) { + __m128i y = _mm_loadu_si128(reinterpret_cast(_y)); + + // Handle anything left over from a previous run that wasn't a multiple of 16 bytes. + if (_rp) { + for (;;) { + if (!len) + return; + --len; + _r[_rp++] = *(in++); + if (_rp == 16) { + y = p_gmacPCLMUL128(_aes._k.ni.h[0], _mm_xor_si128(y, _mm_loadu_si128(reinterpret_cast<__m128i *>(_r)))); + break; + } + } + } + + if (likely(len >= 64)) { + const __m128i sb = s_sseSwapBytes; + const __m128i h = _aes._k.ni.h[0]; + const __m128i hh = _aes._k.ni.h[1]; + const __m128i hhh = _aes._k.ni.h[2]; + const __m128i hhhh = _aes._k.ni.h[3]; + const __m128i h2 = _aes._k.ni.h2[0]; + const __m128i hh2 = _aes._k.ni.h2[1]; + const __m128i hhh2 = _aes._k.ni.h2[2]; + const __m128i hhhh2 = _aes._k.ni.h2[3]; + const uint8_t *const end64 = in + (len & ~((unsigned int)63)); + len &= 63; + do { + __m128i d1 = _mm_shuffle_epi8(_mm_xor_si128(y, _mm_loadu_si128(reinterpret_cast(in))), sb); + __m128i d2 = _mm_shuffle_epi8(_mm_loadu_si128(reinterpret_cast(in + 16)), sb); + __m128i d3 = _mm_shuffle_epi8(_mm_loadu_si128(reinterpret_cast(in + 32)), sb); + __m128i d4 = _mm_shuffle_epi8(_mm_loadu_si128(reinterpret_cast(in + 48)), sb); + in += 64; + __m128i a = _mm_xor_si128(_mm_xor_si128(_mm_clmulepi64_si128(hhhh, d1, 0x00), _mm_clmulepi64_si128(hhh, d2, 0x00)), _mm_xor_si128(_mm_clmulepi64_si128(hh, d3, 0x00), _mm_clmulepi64_si128(h, d4, 0x00))); + __m128i b = _mm_xor_si128(_mm_xor_si128(_mm_clmulepi64_si128(hhhh, d1, 0x11), _mm_clmulepi64_si128(hhh, d2, 0x11)), _mm_xor_si128(_mm_clmulepi64_si128(hh, d3, 0x11), _mm_clmulepi64_si128(h, d4, 0x11))); + __m128i c = _mm_xor_si128(_mm_xor_si128(_mm_xor_si128(_mm_clmulepi64_si128(hhhh2, _mm_xor_si128(_mm_shuffle_epi32(d1, 78), d1), 0x00), _mm_clmulepi64_si128(hhh2, _mm_xor_si128(_mm_shuffle_epi32(d2, 78), d2), 0x00)), _mm_xor_si128(_mm_clmulepi64_si128(hh2, _mm_xor_si128(_mm_shuffle_epi32(d3, 78), d3), 0x00), _mm_clmulepi64_si128(h2, _mm_xor_si128(_mm_shuffle_epi32(d4, 78), d4), 0x00))), _mm_xor_si128(a, b)); + a = _mm_xor_si128(_mm_slli_si128(c, 8), a); + b = _mm_xor_si128(_mm_srli_si128(c, 8), b); + c = _mm_srli_epi32(a, 31); + a = _mm_or_si128(_mm_slli_epi32(a, 1), _mm_slli_si128(c, 4)); + b = _mm_or_si128(_mm_or_si128(_mm_slli_epi32(b, 1), _mm_slli_si128(_mm_srli_epi32(b, 31), 4)), _mm_srli_si128(c, 12)); + c = _mm_xor_si128(_mm_slli_epi32(a, 31), _mm_xor_si128(_mm_slli_epi32(a, 30), _mm_slli_epi32(a, 25))); + a = _mm_xor_si128(a, _mm_slli_si128(c, 12)); + b = _mm_xor_si128(b, _mm_xor_si128(a, _mm_xor_si128(_mm_xor_si128(_mm_srli_epi32(a, 1), _mm_srli_si128(c, 4)), _mm_xor_si128(_mm_srli_epi32(a, 2), _mm_srli_epi32(a, 7))))); + y = _mm_shuffle_epi8(b, sb); + } while (likely(in != end64)); + } + + while (len >= 16) { + y = p_gmacPCLMUL128(_aes._k.ni.h[0], _mm_xor_si128(y, _mm_loadu_si128(reinterpret_cast(in)))); + in += 16; + len -= 16; + } + + _mm_storeu_si128(reinterpret_cast<__m128i *>(_y), y); + + // Any overflow is cached for a later run or finish(). + for (unsigned int i = 0; i < len; ++i) + _r[i] = in[i]; + _rp = len; // len is always less than 16 here + + return; + } +#endif // ZT_AES_AESNI + +#ifdef ZT_AES_NEON + if (Utils::ARMCAP.pmull) { + uint8x16_t y = vld1q_u8(reinterpret_cast(_y)); + const uint8x16_t h = _aes._k.neon.h; + + if (_rp) { + for(;;) { + if (!len) + return; + --len; + _r[_rp++] = *(in++); + if (_rp == 16) { + y = s_clmul_armneon_crypto(h, y, _r); + break; + } + } + } + + while (len >= 16) { + y = s_clmul_armneon_crypto(h, y, in); + in += 16; + len -= 16; + } + + vst1q_u8(reinterpret_cast(_y), y); + + for (unsigned int i = 0; i < len; ++i) + _r[i] = in[i]; + _rp = len; // len is always less than 16 here + + return; + } +#endif // ZT_AES_NEON + + const uint64_t h0 = _aes._k.sw.h[0]; + const uint64_t h1 = _aes._k.sw.h[1]; + uint64_t y0 = _y[0]; + uint64_t y1 = _y[1]; + + if (_rp) { + for (;;) { + if (!len) + return; + --len; + _r[_rp++] = *(in++); + if (_rp == 16) { + y0 ^= Utils::loadMachineEndian< uint64_t >(_r); + y1 ^= Utils::loadMachineEndian< uint64_t >(_r + 8); + s_gfmul(h0, h1, y0, y1); + break; + } + } + } + + if (likely(((uintptr_t)in & 7U) == 0U)) { + while (len >= 16) { + y0 ^= *reinterpret_cast(in); + y1 ^= *reinterpret_cast(in + 8); + in += 16; + s_gfmul(h0, h1, y0, y1); + len -= 16; + } + } else { + while (len >= 16) { + y0 ^= Utils::loadMachineEndian< uint64_t >(in); + y1 ^= Utils::loadMachineEndian< uint64_t >(in + 8); + in += 16; + s_gfmul(h0, h1, y0, y1); + len -= 16; + } + } + + _y[0] = y0; + _y[1] = y1; + + for (unsigned int i = 0; i < len; ++i) + _r[i] = in[i]; + _rp = len; // len is always less than 16 here +} + +void AES::GMAC::finish(uint8_t tag[16]) noexcept +{ +#ifdef ZT_AES_AESNI + if (likely(Utils::CPUID.aes)) { + __m128i y = _mm_loadu_si128(reinterpret_cast(_y)); + + // Handle any remaining bytes, padding the last block with zeroes. + if (_rp) { + while (_rp < 16) + _r[_rp++] = 0; + y = p_gmacPCLMUL128(_aes._k.ni.h[0], _mm_xor_si128(y, _mm_loadu_si128(reinterpret_cast<__m128i *>(_r)))); + } + + // Interleave encryption of IV with the final GHASH of y XOR (length * 8). + // Then XOR these together to get the final tag. + const __m128i *const k = _aes._k.ni.k; + const __m128i h = _aes._k.ni.h[0]; + y = _mm_xor_si128(y, _mm_set_epi64x(0LL, (long long)Utils::hton((uint64_t)_len << 3U))); + y = _mm_shuffle_epi8(y, s_sseSwapBytes); + __m128i encIV = _mm_xor_si128(_mm_loadu_si128(reinterpret_cast(_iv)), k[0]); + __m128i t1 = _mm_clmulepi64_si128(h, y, 0x00); + __m128i t2 = _mm_clmulepi64_si128(h, y, 0x01); + __m128i t3 = _mm_clmulepi64_si128(h, y, 0x10); + __m128i t4 = _mm_clmulepi64_si128(h, y, 0x11); + encIV = _mm_aesenc_si128(encIV, k[1]); + t2 = _mm_xor_si128(t2, t3); + t3 = _mm_slli_si128(t2, 8); + encIV = _mm_aesenc_si128(encIV, k[2]); + t2 = _mm_srli_si128(t2, 8); + t1 = _mm_xor_si128(t1, t3); + encIV = _mm_aesenc_si128(encIV, k[3]); + t4 = _mm_xor_si128(t4, t2); + __m128i t5 = _mm_srli_epi32(t1, 31); + t1 = _mm_slli_epi32(t1, 1); + __m128i t6 = _mm_srli_epi32(t4, 31); + encIV = _mm_aesenc_si128(encIV, k[4]); + t4 = _mm_slli_epi32(t4, 1); + t3 = _mm_srli_si128(t5, 12); + encIV = _mm_aesenc_si128(encIV, k[5]); + t6 = _mm_slli_si128(t6, 4); + t5 = _mm_slli_si128(t5, 4); + encIV = _mm_aesenc_si128(encIV, k[6]); + t1 = _mm_or_si128(t1, t5); + t4 = _mm_or_si128(t4, t6); + encIV = _mm_aesenc_si128(encIV, k[7]); + t4 = _mm_or_si128(t4, t3); + t5 = _mm_slli_epi32(t1, 31); + encIV = _mm_aesenc_si128(encIV, k[8]); + t6 = _mm_slli_epi32(t1, 30); + t3 = _mm_slli_epi32(t1, 25); + encIV = _mm_aesenc_si128(encIV, k[9]); + t5 = _mm_xor_si128(t5, t6); + t5 = _mm_xor_si128(t5, t3); + encIV = _mm_aesenc_si128(encIV, k[10]); + t6 = _mm_srli_si128(t5, 4); + t4 = _mm_xor_si128(t4, t6); + encIV = _mm_aesenc_si128(encIV, k[11]); + t5 = _mm_slli_si128(t5, 12); + t1 = _mm_xor_si128(t1, t5); + t4 = _mm_xor_si128(t4, t1); + t5 = _mm_srli_epi32(t1, 1); + encIV = _mm_aesenc_si128(encIV, k[12]); + t2 = _mm_srli_epi32(t1, 2); + t3 = _mm_srli_epi32(t1, 7); + encIV = _mm_aesenc_si128(encIV, k[13]); + t4 = _mm_xor_si128(t4, t2); + t4 = _mm_xor_si128(t4, t3); + encIV = _mm_aesenclast_si128(encIV, k[14]); + t4 = _mm_xor_si128(t4, t5); + _mm_storeu_si128(reinterpret_cast<__m128i *>(tag), _mm_xor_si128(_mm_shuffle_epi8(t4, s_sseSwapBytes), encIV)); + + return; + } +#endif // ZT_AES_AESNI + +#ifdef ZT_AES_NEON + if (Utils::ARMCAP.pmull) { + uint64_t tmp[2]; + uint8x16_t y = vld1q_u8(reinterpret_cast(_y)); + const uint8x16_t h = _aes._k.neon.h; + + if (_rp) { + while (_rp < 16) + _r[_rp++] = 0; + y = s_clmul_armneon_crypto(h, y, _r); + } + + tmp[0] = Utils::hton((uint64_t)_len << 3U); + tmp[1] = 0; + y = s_clmul_armneon_crypto(h, y, reinterpret_cast(tmp)); + + Utils::copy< 12 >(tmp, _iv); +#if __BYTE_ORDER == __BIG_ENDIAN + reinterpret_cast(tmp)[3] = 0x00000001; +#else + reinterpret_cast(tmp)[3] = 0x01000000; +#endif + _aes.encrypt(tmp, tmp); + + uint8x16_t yy = y; + Utils::storeMachineEndian< uint64_t >(tag, tmp[0] ^ reinterpret_cast(&yy)[0]); + Utils::storeMachineEndian< uint64_t >(tag + 8, tmp[1] ^ reinterpret_cast(&yy)[1]); + + return; + } +#endif // ZT_AES_NEON + + const uint64_t h0 = _aes._k.sw.h[0]; + const uint64_t h1 = _aes._k.sw.h[1]; + uint64_t y0 = _y[0]; + uint64_t y1 = _y[1]; + + if (_rp) { + while (_rp < 16) + _r[_rp++] = 0; + y0 ^= Utils::loadMachineEndian< uint64_t >(_r); + y1 ^= Utils::loadMachineEndian< uint64_t >(_r + 8); + s_gfmul(h0, h1, y0, y1); + } + + y0 ^= Utils::hton((uint64_t)_len << 3U); + s_gfmul(h0, h1, y0, y1); + + uint64_t iv2[2]; + Utils::copy< 12 >(iv2, _iv); +#if __BYTE_ORDER == __BIG_ENDIAN + reinterpret_cast(iv2)[3] = 0x00000001; +#else + reinterpret_cast(iv2)[3] = 0x01000000; +#endif + _aes.encrypt(iv2, iv2); + + Utils::storeMachineEndian< uint64_t >(tag, iv2[0] ^ y0); + Utils::storeMachineEndian< uint64_t >(tag + 8, iv2[1] ^ y1); +} + +// AES-CTR ------------------------------------------------------------------------------------------------------------ + +#ifdef ZT_AES_AESNI + +/* Disable VAES stuff on compilers too old to compile these intrinsics, + * and MinGW64 also seems not to support them so disable on Windows. + * The performance gain can be significant but regular SSE is already so + * fast it's highly unlikely to be a rate limiting factor except on massive + * servers and network infrastructure stuff. */ +#if !defined(__WINDOWS__) && ((__GNUC__ >= 8) || (__clang_major__ >= 7)) + +#define ZT_AES_VAES512 1 + +static +__attribute__((__target__("sse4,avx,avx2,vaes,avx512f,avx512bw"))) +void p_aesCtrInnerVAES512(unsigned int &len, const uint64_t c0, uint64_t &c1, const uint8_t *&in, uint8_t *&out, const __m128i *const k) noexcept +{ + const __m512i kk0 = _mm512_broadcast_i32x4(k[0]); + const __m512i kk1 = _mm512_broadcast_i32x4(k[1]); + const __m512i kk2 = _mm512_broadcast_i32x4(k[2]); + const __m512i kk3 = _mm512_broadcast_i32x4(k[3]); + const __m512i kk4 = _mm512_broadcast_i32x4(k[4]); + const __m512i kk5 = _mm512_broadcast_i32x4(k[5]); + const __m512i kk6 = _mm512_broadcast_i32x4(k[6]); + const __m512i kk7 = _mm512_broadcast_i32x4(k[7]); + const __m512i kk8 = _mm512_broadcast_i32x4(k[8]); + const __m512i kk9 = _mm512_broadcast_i32x4(k[9]); + const __m512i kk10 = _mm512_broadcast_i32x4(k[10]); + const __m512i kk11 = _mm512_broadcast_i32x4(k[11]); + const __m512i kk12 = _mm512_broadcast_i32x4(k[12]); + const __m512i kk13 = _mm512_broadcast_i32x4(k[13]); + const __m512i kk14 = _mm512_broadcast_i32x4(k[14]); + do { + __m512i p0 = _mm512_loadu_si512(reinterpret_cast(in)); + __m512i d0 = _mm512_set_epi64( + (long long)Utils::hton(c1 + 3ULL), (long long)c0, + (long long)Utils::hton(c1 + 2ULL), (long long)c0, + (long long)Utils::hton(c1 + 1ULL), (long long)c0, + (long long)Utils::hton(c1), (long long)c0); + c1 += 4; + in += 64; + len -= 64; + d0 = _mm512_xor_si512(d0, kk0); + d0 = _mm512_aesenc_epi128(d0, kk1); + d0 = _mm512_aesenc_epi128(d0, kk2); + d0 = _mm512_aesenc_epi128(d0, kk3); + d0 = _mm512_aesenc_epi128(d0, kk4); + d0 = _mm512_aesenc_epi128(d0, kk5); + d0 = _mm512_aesenc_epi128(d0, kk6); + d0 = _mm512_aesenc_epi128(d0, kk7); + d0 = _mm512_aesenc_epi128(d0, kk8); + d0 = _mm512_aesenc_epi128(d0, kk9); + d0 = _mm512_aesenc_epi128(d0, kk10); + d0 = _mm512_aesenc_epi128(d0, kk11); + d0 = _mm512_aesenc_epi128(d0, kk12); + d0 = _mm512_aesenc_epi128(d0, kk13); + d0 = _mm512_aesenclast_epi128(d0, kk14); + _mm512_storeu_si512(reinterpret_cast<__m512i *>(out), _mm512_xor_si512(p0, d0)); + out += 64; + } while (likely(len >= 64)); +} + +#define ZT_AES_VAES256 1 + +static +__attribute__((__target__("sse4,avx,avx2,vaes"))) +void p_aesCtrInnerVAES256(unsigned int &len, const uint64_t c0, uint64_t &c1, const uint8_t *&in, uint8_t *&out, const __m128i *const k) noexcept +{ + const __m256i kk0 = _mm256_broadcastsi128_si256(k[0]); + const __m256i kk1 = _mm256_broadcastsi128_si256(k[1]); + const __m256i kk2 = _mm256_broadcastsi128_si256(k[2]); + const __m256i kk3 = _mm256_broadcastsi128_si256(k[3]); + const __m256i kk4 = _mm256_broadcastsi128_si256(k[4]); + const __m256i kk5 = _mm256_broadcastsi128_si256(k[5]); + const __m256i kk6 = _mm256_broadcastsi128_si256(k[6]); + const __m256i kk7 = _mm256_broadcastsi128_si256(k[7]); + const __m256i kk8 = _mm256_broadcastsi128_si256(k[8]); + const __m256i kk9 = _mm256_broadcastsi128_si256(k[9]); + const __m256i kk10 = _mm256_broadcastsi128_si256(k[10]); + const __m256i kk11 = _mm256_broadcastsi128_si256(k[11]); + const __m256i kk12 = _mm256_broadcastsi128_si256(k[12]); + const __m256i kk13 = _mm256_broadcastsi128_si256(k[13]); + const __m256i kk14 = _mm256_broadcastsi128_si256(k[14]); + do { + __m256i p0 = _mm256_loadu_si256(reinterpret_cast(in)); + __m256i p1 = _mm256_loadu_si256(reinterpret_cast(in + 32)); + __m256i d0 = _mm256_set_epi64x( + (long long)Utils::hton(c1 + 1ULL), (long long)c0, + (long long)Utils::hton(c1), (long long)c0); + __m256i d1 = _mm256_set_epi64x( + (long long)Utils::hton(c1 + 3ULL), (long long)c0, + (long long)Utils::hton(c1 + 2ULL), (long long)c0); + c1 += 4; + in += 64; + len -= 64; + d0 = _mm256_xor_si256(d0, kk0); + d1 = _mm256_xor_si256(d1, kk0); + d0 = _mm256_aesenc_epi128(d0, kk1); + d1 = _mm256_aesenc_epi128(d1, kk1); + d0 = _mm256_aesenc_epi128(d0, kk2); + d1 = _mm256_aesenc_epi128(d1, kk2); + d0 = _mm256_aesenc_epi128(d0, kk3); + d1 = _mm256_aesenc_epi128(d1, kk3); + d0 = _mm256_aesenc_epi128(d0, kk4); + d1 = _mm256_aesenc_epi128(d1, kk4); + d0 = _mm256_aesenc_epi128(d0, kk5); + d1 = _mm256_aesenc_epi128(d1, kk5); + d0 = _mm256_aesenc_epi128(d0, kk6); + d1 = _mm256_aesenc_epi128(d1, kk6); + d0 = _mm256_aesenc_epi128(d0, kk7); + d1 = _mm256_aesenc_epi128(d1, kk7); + d0 = _mm256_aesenc_epi128(d0, kk8); + d1 = _mm256_aesenc_epi128(d1, kk8); + d0 = _mm256_aesenc_epi128(d0, kk9); + d1 = _mm256_aesenc_epi128(d1, kk9); + d0 = _mm256_aesenc_epi128(d0, kk10); + d1 = _mm256_aesenc_epi128(d1, kk10); + d0 = _mm256_aesenc_epi128(d0, kk11); + d1 = _mm256_aesenc_epi128(d1, kk11); + d0 = _mm256_aesenc_epi128(d0, kk12); + d1 = _mm256_aesenc_epi128(d1, kk12); + d0 = _mm256_aesenc_epi128(d0, kk13); + d1 = _mm256_aesenc_epi128(d1, kk13); + d0 = _mm256_aesenclast_epi128(d0, kk14); + d1 = _mm256_aesenclast_epi128(d1, kk14); + _mm256_storeu_si256(reinterpret_cast<__m256i *>(out), _mm256_xor_si256(d0, p0)); + _mm256_storeu_si256(reinterpret_cast<__m256i *>(out + 32), _mm256_xor_si256(d1, p1)); + out += 64; + } while (likely(len >= 64)); +} + +#endif // does compiler support AVX2 and AVX512 AES intrinsics? + +#endif // ZT_AES_AESNI + +void AES::CTR::crypt(const void *const input, unsigned int len) noexcept +{ + const uint8_t *in = reinterpret_cast(input); + uint8_t *out = _out; + +#ifdef ZT_AES_AESNI + if (likely(Utils::CPUID.aes)) { + const __m128i dd = _mm_set_epi64x(0, (long long)_ctr[0]); + uint64_t c1 = Utils::ntoh(_ctr[1]); + + const __m128i *const k = _aes._k.ni.k; + const __m128i k0 = k[0]; + const __m128i k1 = k[1]; + const __m128i k2 = k[2]; + const __m128i k3 = k[3]; + const __m128i k4 = k[4]; + const __m128i k5 = k[5]; + const __m128i k6 = k[6]; + const __m128i k7 = k[7]; + const __m128i k8 = k[8]; + const __m128i k9 = k[9]; + const __m128i k10 = k[10]; + const __m128i k11 = k[11]; + const __m128i k12 = k[12]; + const __m128i k13 = k[13]; + const __m128i k14 = k[14]; + + // Complete any unfinished blocks from previous calls to crypt(). + unsigned int totalLen = _len; + if ((totalLen & 15U)) { + for (;;) { + if (unlikely(!len)) { + _ctr[1] = Utils::hton(c1); + _len = totalLen; + return; + } + --len; + out[totalLen++] = *(in++); + if (!(totalLen & 15U)) { + __m128i d0 = _mm_insert_epi64(dd, (long long)Utils::hton(c1++), 1); + d0 = _mm_xor_si128(d0, k0); + d0 = _mm_aesenc_si128(d0, k1); + d0 = _mm_aesenc_si128(d0, k2); + d0 = _mm_aesenc_si128(d0, k3); + d0 = _mm_aesenc_si128(d0, k4); + d0 = _mm_aesenc_si128(d0, k5); + d0 = _mm_aesenc_si128(d0, k6); + d0 = _mm_aesenc_si128(d0, k7); + d0 = _mm_aesenc_si128(d0, k8); + d0 = _mm_aesenc_si128(d0, k9); + d0 = _mm_aesenc_si128(d0, k10); + __m128i *const outblk = reinterpret_cast<__m128i *>(out + (totalLen - 16)); + d0 = _mm_aesenc_si128(d0, k11); + const __m128i p0 = _mm_loadu_si128(outblk); + d0 = _mm_aesenc_si128(d0, k12); + d0 = _mm_aesenc_si128(d0, k13); + d0 = _mm_aesenclast_si128(d0, k14); + _mm_storeu_si128(outblk, _mm_xor_si128(p0, d0)); + break; + } + } + } + + out += totalLen; + _len = totalLen + len; + + if (likely(len >= 64)) { + +#if defined(ZT_AES_VAES512) && defined(ZT_AES_VAES256) + if (Utils::CPUID.vaes && (len >= 256)) { + if (Utils::CPUID.avx512f) { + p_aesCtrInnerVAES512(len, _ctr[0], c1, in, out, k); + } else { + p_aesCtrInnerVAES256(len, _ctr[0], c1, in, out, k); + } + goto skip_conventional_aesni_64; + } +#endif + +#if !defined(ZT_AES_VAES512) && defined(ZT_AES_VAES256) + if (Utils::CPUID.vaes && (len >= 256)) { + p_aesCtrInnerVAES256(len, _ctr[0], c1, in, out, k); + goto skip_conventional_aesni_64; + } +#endif + + const uint8_t *const eof64 = in + (len & ~((unsigned int)63)); + len &= 63; + __m128i d0, d1, d2, d3; + do { + const uint64_t c10 = Utils::hton(c1); + const uint64_t c11 = Utils::hton(c1 + 1ULL); + const uint64_t c12 = Utils::hton(c1 + 2ULL); + const uint64_t c13 = Utils::hton(c1 + 3ULL); + d0 = _mm_insert_epi64(dd, (long long)c10, 1); + d1 = _mm_insert_epi64(dd, (long long)c11, 1); + d2 = _mm_insert_epi64(dd, (long long)c12, 1); + d3 = _mm_insert_epi64(dd, (long long)c13, 1); + c1 += 4; + d0 = _mm_xor_si128(d0, k0); + d1 = _mm_xor_si128(d1, k0); + d2 = _mm_xor_si128(d2, k0); + d3 = _mm_xor_si128(d3, k0); + d0 = _mm_aesenc_si128(d0, k1); + d1 = _mm_aesenc_si128(d1, k1); + d2 = _mm_aesenc_si128(d2, k1); + d3 = _mm_aesenc_si128(d3, k1); + d0 = _mm_aesenc_si128(d0, k2); + d1 = _mm_aesenc_si128(d1, k2); + d2 = _mm_aesenc_si128(d2, k2); + d3 = _mm_aesenc_si128(d3, k2); + d0 = _mm_aesenc_si128(d0, k3); + d1 = _mm_aesenc_si128(d1, k3); + d2 = _mm_aesenc_si128(d2, k3); + d3 = _mm_aesenc_si128(d3, k3); + d0 = _mm_aesenc_si128(d0, k4); + d1 = _mm_aesenc_si128(d1, k4); + d2 = _mm_aesenc_si128(d2, k4); + d3 = _mm_aesenc_si128(d3, k4); + d0 = _mm_aesenc_si128(d0, k5); + d1 = _mm_aesenc_si128(d1, k5); + d2 = _mm_aesenc_si128(d2, k5); + d3 = _mm_aesenc_si128(d3, k5); + d0 = _mm_aesenc_si128(d0, k6); + d1 = _mm_aesenc_si128(d1, k6); + d2 = _mm_aesenc_si128(d2, k6); + d3 = _mm_aesenc_si128(d3, k6); + d0 = _mm_aesenc_si128(d0, k7); + d1 = _mm_aesenc_si128(d1, k7); + d2 = _mm_aesenc_si128(d2, k7); + d3 = _mm_aesenc_si128(d3, k7); + d0 = _mm_aesenc_si128(d0, k8); + d1 = _mm_aesenc_si128(d1, k8); + d2 = _mm_aesenc_si128(d2, k8); + d3 = _mm_aesenc_si128(d3, k8); + d0 = _mm_aesenc_si128(d0, k9); + d1 = _mm_aesenc_si128(d1, k9); + d2 = _mm_aesenc_si128(d2, k9); + d3 = _mm_aesenc_si128(d3, k9); + d0 = _mm_aesenc_si128(d0, k10); + d1 = _mm_aesenc_si128(d1, k10); + d2 = _mm_aesenc_si128(d2, k10); + d3 = _mm_aesenc_si128(d3, k10); + d0 = _mm_aesenc_si128(d0, k11); + d1 = _mm_aesenc_si128(d1, k11); + d2 = _mm_aesenc_si128(d2, k11); + d3 = _mm_aesenc_si128(d3, k11); + d0 = _mm_aesenc_si128(d0, k12); + d1 = _mm_aesenc_si128(d1, k12); + d2 = _mm_aesenc_si128(d2, k12); + d3 = _mm_aesenc_si128(d3, k12); + d0 = _mm_aesenc_si128(d0, k13); + d1 = _mm_aesenc_si128(d1, k13); + d2 = _mm_aesenc_si128(d2, k13); + d3 = _mm_aesenc_si128(d3, k13); + d0 = _mm_xor_si128(_mm_aesenclast_si128(d0, k14), _mm_loadu_si128(reinterpret_cast(in))); + d1 = _mm_xor_si128(_mm_aesenclast_si128(d1, k14), _mm_loadu_si128(reinterpret_cast(in + 16))); + d2 = _mm_xor_si128(_mm_aesenclast_si128(d2, k14), _mm_loadu_si128(reinterpret_cast(in + 32))); + d3 = _mm_xor_si128(_mm_aesenclast_si128(d3, k14), _mm_loadu_si128(reinterpret_cast(in + 48))); + in += 64; + _mm_storeu_si128(reinterpret_cast<__m128i *>(out), d0); + _mm_storeu_si128(reinterpret_cast<__m128i *>(out + 16), d1); + _mm_storeu_si128(reinterpret_cast<__m128i *>(out + 32), d2); + _mm_storeu_si128(reinterpret_cast<__m128i *>(out + 48), d3); + out += 64; + } while (likely(in != eof64)); + + } + + skip_conventional_aesni_64: + while (len >= 16) { + __m128i d0 = _mm_insert_epi64(dd, (long long)Utils::hton(c1++), 1); + d0 = _mm_xor_si128(d0, k0); + d0 = _mm_aesenc_si128(d0, k1); + d0 = _mm_aesenc_si128(d0, k2); + d0 = _mm_aesenc_si128(d0, k3); + d0 = _mm_aesenc_si128(d0, k4); + d0 = _mm_aesenc_si128(d0, k5); + d0 = _mm_aesenc_si128(d0, k6); + d0 = _mm_aesenc_si128(d0, k7); + d0 = _mm_aesenc_si128(d0, k8); + d0 = _mm_aesenc_si128(d0, k9); + d0 = _mm_aesenc_si128(d0, k10); + d0 = _mm_aesenc_si128(d0, k11); + d0 = _mm_aesenc_si128(d0, k12); + d0 = _mm_aesenc_si128(d0, k13); + _mm_storeu_si128(reinterpret_cast<__m128i *>(out), _mm_xor_si128(_mm_aesenclast_si128(d0, k14), _mm_loadu_si128(reinterpret_cast(in)))); + in += 16; + len -= 16; + out += 16; + } + + // Any remaining input is placed in _out. This will be picked up and crypted + // on subsequent calls to crypt() or finish() as it'll mean _len will not be + // an even multiple of 16. + for (unsigned int i = 0; i < len; ++i) + out[i] = in[i]; + + _ctr[1] = Utils::hton(c1); + return; + } +#endif // ZT_AES_AESNI + +#ifdef ZT_AES_NEON + if (Utils::ARMCAP.aes) { + uint8x16_t dd = vrev32q_u8(vld1q_u8(reinterpret_cast(_ctr))); + const uint32x4_t one = {0,0,0,1}; + + uint8x16_t k0 = _aes._k.neon.ek[0]; + uint8x16_t k1 = _aes._k.neon.ek[1]; + uint8x16_t k2 = _aes._k.neon.ek[2]; + uint8x16_t k3 = _aes._k.neon.ek[3]; + uint8x16_t k4 = _aes._k.neon.ek[4]; + uint8x16_t k5 = _aes._k.neon.ek[5]; + uint8x16_t k6 = _aes._k.neon.ek[6]; + uint8x16_t k7 = _aes._k.neon.ek[7]; + uint8x16_t k8 = _aes._k.neon.ek[8]; + uint8x16_t k9 = _aes._k.neon.ek[9]; + uint8x16_t k10 = _aes._k.neon.ek[10]; + uint8x16_t k11 = _aes._k.neon.ek[11]; + uint8x16_t k12 = _aes._k.neon.ek[12]; + uint8x16_t k13 = _aes._k.neon.ek[13]; + uint8x16_t k14 = _aes._k.neon.ek[14]; + + unsigned int totalLen = _len; + if ((totalLen & 15U)) { + for (;;) { + if (unlikely(!len)) { + vst1q_u8(reinterpret_cast(_ctr), vrev32q_u8(dd)); + _len = totalLen; + return; + } + --len; + out[totalLen++] = *(in++); + if (!(totalLen & 15U)) { + uint8_t *const otmp = out + (totalLen - 16); + uint8x16_t d0 = vrev32q_u8(dd); + uint8x16_t pt = vld1q_u8(otmp); + d0 = vaesmcq_u8(vaeseq_u8(d0, k0)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k1)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k2)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k3)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k4)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k5)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k6)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k7)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k8)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k9)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k10)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k11)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k12)); + d0 = veorq_u8(vaeseq_u8(d0, k13), k14); + vst1q_u8(otmp, veorq_u8(pt, d0)); + dd = (uint8x16_t)vaddq_u32((uint32x4_t)dd, one); + break; + } + } + } + + out += totalLen; + _len = totalLen + len; + + if (likely(len >= 64)) { + const uint32x4_t four = vshlq_n_u32(one, 2); + uint8x16_t dd1 = (uint8x16_t)vaddq_u32((uint32x4_t)dd, one); + uint8x16_t dd2 = (uint8x16_t)vaddq_u32((uint32x4_t)dd1, one); + uint8x16_t dd3 = (uint8x16_t)vaddq_u32((uint32x4_t)dd2, one); + for (;;) { + len -= 64; + uint8x16_t d0 = vrev32q_u8(dd); + uint8x16_t d1 = vrev32q_u8(dd1); + uint8x16_t d2 = vrev32q_u8(dd2); + uint8x16_t d3 = vrev32q_u8(dd3); + uint8x16_t pt0 = vld1q_u8(in); + in += 16; + d0 = vaesmcq_u8(vaeseq_u8(d0, k0)); + d1 = vaesmcq_u8(vaeseq_u8(d1, k0)); + d2 = vaesmcq_u8(vaeseq_u8(d2, k0)); + d3 = vaesmcq_u8(vaeseq_u8(d3, k0)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k1)); + d1 = vaesmcq_u8(vaeseq_u8(d1, k1)); + d2 = vaesmcq_u8(vaeseq_u8(d2, k1)); + d3 = vaesmcq_u8(vaeseq_u8(d3, k1)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k2)); + d1 = vaesmcq_u8(vaeseq_u8(d1, k2)); + d2 = vaesmcq_u8(vaeseq_u8(d2, k2)); + d3 = vaesmcq_u8(vaeseq_u8(d3, k2)); + uint8x16_t pt1 = vld1q_u8(in); + in += 16; + d0 = vaesmcq_u8(vaeseq_u8(d0, k3)); + d1 = vaesmcq_u8(vaeseq_u8(d1, k3)); + d2 = vaesmcq_u8(vaeseq_u8(d2, k3)); + d3 = vaesmcq_u8(vaeseq_u8(d3, k3)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k4)); + d1 = vaesmcq_u8(vaeseq_u8(d1, k4)); + d2 = vaesmcq_u8(vaeseq_u8(d2, k4)); + d3 = vaesmcq_u8(vaeseq_u8(d3, k4)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k5)); + d1 = vaesmcq_u8(vaeseq_u8(d1, k5)); + d2 = vaesmcq_u8(vaeseq_u8(d2, k5)); + d3 = vaesmcq_u8(vaeseq_u8(d3, k5)); + uint8x16_t pt2 = vld1q_u8(in); + in += 16; + d0 = vaesmcq_u8(vaeseq_u8(d0, k6)); + d1 = vaesmcq_u8(vaeseq_u8(d1, k6)); + d2 = vaesmcq_u8(vaeseq_u8(d2, k6)); + d3 = vaesmcq_u8(vaeseq_u8(d3, k6)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k7)); + d1 = vaesmcq_u8(vaeseq_u8(d1, k7)); + d2 = vaesmcq_u8(vaeseq_u8(d2, k7)); + d3 = vaesmcq_u8(vaeseq_u8(d3, k7)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k8)); + d1 = vaesmcq_u8(vaeseq_u8(d1, k8)); + d2 = vaesmcq_u8(vaeseq_u8(d2, k8)); + d3 = vaesmcq_u8(vaeseq_u8(d3, k8)); + uint8x16_t pt3 = vld1q_u8(in); + in += 16; + d0 = vaesmcq_u8(vaeseq_u8(d0, k9)); + d1 = vaesmcq_u8(vaeseq_u8(d1, k9)); + d2 = vaesmcq_u8(vaeseq_u8(d2, k9)); + d3 = vaesmcq_u8(vaeseq_u8(d3, k9)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k10)); + d1 = vaesmcq_u8(vaeseq_u8(d1, k10)); + d2 = vaesmcq_u8(vaeseq_u8(d2, k10)); + d3 = vaesmcq_u8(vaeseq_u8(d3, k10)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k11)); + d1 = vaesmcq_u8(vaeseq_u8(d1, k11)); + d2 = vaesmcq_u8(vaeseq_u8(d2, k11)); + d3 = vaesmcq_u8(vaeseq_u8(d3, k11)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k12)); + d1 = vaesmcq_u8(vaeseq_u8(d1, k12)); + d2 = vaesmcq_u8(vaeseq_u8(d2, k12)); + d3 = vaesmcq_u8(vaeseq_u8(d3, k12)); + d0 = veorq_u8(vaeseq_u8(d0, k13), k14); + d1 = veorq_u8(vaeseq_u8(d1, k13), k14); + d2 = veorq_u8(vaeseq_u8(d2, k13), k14); + d3 = veorq_u8(vaeseq_u8(d3, k13), k14); + + d0 = veorq_u8(pt0, d0); + d1 = veorq_u8(pt1, d1); + d2 = veorq_u8(pt2, d2); + d3 = veorq_u8(pt3, d3); + + vst1q_u8(out, d0); + vst1q_u8(out + 16, d1); + vst1q_u8(out + 32, d2); + vst1q_u8(out + 48, d3); + out += 64; + + dd = (uint8x16_t)vaddq_u32((uint32x4_t)dd, four); + if (unlikely(len < 64)) + break; + dd1 = (uint8x16_t)vaddq_u32((uint32x4_t)dd1, four); + dd2 = (uint8x16_t)vaddq_u32((uint32x4_t)dd2, four); + dd3 = (uint8x16_t)vaddq_u32((uint32x4_t)dd3, four); + } + } + + while (len >= 16) { + len -= 16; + uint8x16_t d0 = vrev32q_u8(dd); + uint8x16_t pt = vld1q_u8(in); + in += 16; + dd = (uint8x16_t)vaddq_u32((uint32x4_t)dd, one); + d0 = vaesmcq_u8(vaeseq_u8(d0, k0)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k1)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k2)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k3)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k4)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k5)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k6)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k7)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k8)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k9)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k10)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k11)); + d0 = vaesmcq_u8(vaeseq_u8(d0, k12)); + d0 = veorq_u8(vaeseq_u8(d0, k13), k14); + vst1q_u8(out, veorq_u8(pt, d0)); + out += 16; + } + + // Any remaining input is placed in _out. This will be picked up and crypted + // on subsequent calls to crypt() or finish() as it'll mean _len will not be + // an even multiple of 16. + for (unsigned int i = 0; i < len; ++i) + out[i] = in[i]; + + vst1q_u8(reinterpret_cast(_ctr), vrev32q_u8(dd)); + return; + } +#endif // ZT_AES_NEON + + uint64_t keyStream[2]; + uint32_t ctr = Utils::ntoh(reinterpret_cast(_ctr)[3]); + + unsigned int totalLen = _len; + if ((totalLen & 15U)) { + for (;;) { + if (!len) { + _len = (totalLen + len); + return; + } + --len; + out[totalLen++] = *(in++); + if (!(totalLen & 15U)) { + _aes._encryptSW(reinterpret_cast(_ctr), reinterpret_cast(keyStream)); + reinterpret_cast(_ctr)[3] = Utils::hton(++ctr); + uint8_t *outblk = out + (totalLen - 16); + for (int i = 0; i < 16; ++i) + outblk[i] ^= reinterpret_cast(keyStream)[i]; + break; + } + } + } + + out += totalLen; + _len = (totalLen + len); + + if (likely(len >= 16)) { + const uint32_t *const restrict rk = _aes._k.sw.ek; + const uint32_t ctr0rk0 = Utils::ntoh(reinterpret_cast(_ctr)[0]) ^rk[0]; + const uint32_t ctr1rk1 = Utils::ntoh(reinterpret_cast(_ctr)[1]) ^rk[1]; + const uint32_t ctr2rk2 = Utils::ntoh(reinterpret_cast(_ctr)[2]) ^rk[2]; + const uint32_t m8 = 0x000000ff; + const uint32_t m8_8 = 0x0000ff00; + const uint32_t m8_16 = 0x00ff0000; + const uint32_t m8_24 = 0xff000000; + if (likely((((uintptr_t)out & 7U) == 0U) && (((uintptr_t)in & 7U) == 0U))) { + do { + uint32_t s0, s1, s2, s3, t0, t1, t2, t3; + s0 = ctr0rk0; + s1 = ctr1rk1; + s2 = ctr2rk2; + s3 = ctr++ ^ rk[3]; + + const uint64_t in0 = *reinterpret_cast(in); + const uint64_t in1 = *reinterpret_cast(in + 8); + in += 16; + + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[4]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[5]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[6]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[7]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[8]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[9]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[10]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[11]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[12]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[13]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[14]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[15]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[16]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[17]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[18]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[19]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[20]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[21]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[22]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[23]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[24]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[25]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[26]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[27]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[28]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[29]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[30]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[31]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[32]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[33]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[34]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[35]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[36]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[37]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[38]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[39]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[40]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[41]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[42]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[43]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[44]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[45]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[46]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[47]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[48]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[49]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[50]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[51]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[52]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[53]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[54]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[55]; + s0 = (Te2_r(t0 >> 24U) & m8_24) ^ (Te3_r((t1 >> 16U) & m8) & m8_16) ^ (Te0[(t2 >> 8U) & m8] & m8_8) ^ (Te1_r(t3 & m8) & m8) ^ rk[56]; + s1 = (Te2_r(t1 >> 24U) & m8_24) ^ (Te3_r((t2 >> 16U) & m8) & m8_16) ^ (Te0[(t3 >> 8U) & m8] & m8_8) ^ (Te1_r(t0 & m8) & m8) ^ rk[57]; + s2 = (Te2_r(t2 >> 24U) & m8_24) ^ (Te3_r((t3 >> 16U) & m8) & m8_16) ^ (Te0[(t0 >> 8U) & m8] & m8_8) ^ (Te1_r(t1 & m8) & m8) ^ rk[58]; + s3 = (Te2_r(t3 >> 24U) & m8_24) ^ (Te3_r((t0 >> 16U) & m8) & m8_16) ^ (Te0[(t1 >> 8U) & m8] & m8_8) ^ (Te1_r(t2 & m8) & m8) ^ rk[59]; + + *reinterpret_cast(out) = in0 ^ Utils::hton(((uint64_t)s0 << 32U) | (uint64_t)s1); + *reinterpret_cast(out + 8) = in1 ^ Utils::hton(((uint64_t)s2 << 32U) | (uint64_t)s3); + out += 16; + } while ((len -= 16) >= 16); + } else { + do { + uint32_t s0, s1, s2, s3, t0, t1, t2, t3; + s0 = ctr0rk0; + s1 = ctr1rk1; + s2 = ctr2rk2; + s3 = ctr++ ^ rk[3]; + + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[4]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[5]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[6]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[7]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[8]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[9]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[10]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[11]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[12]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[13]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[14]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[15]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[16]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[17]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[18]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[19]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[20]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[21]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[22]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[23]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[24]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[25]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[26]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[27]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[28]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[29]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[30]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[31]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[32]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[33]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[34]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[35]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[36]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[37]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[38]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[39]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[40]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[41]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[42]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[43]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[44]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[45]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[46]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[47]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[48]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[49]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[50]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[51]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[52]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[53]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[54]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[55]; + s0 = (Te2_r(t0 >> 24U) & m8_24) ^ (Te3_r((t1 >> 16U) & m8) & m8_16) ^ (Te0[(t2 >> 8U) & m8] & m8_8) ^ (Te1_r(t3 & m8) & m8) ^ rk[56]; + s1 = (Te2_r(t1 >> 24U) & m8_24) ^ (Te3_r((t2 >> 16U) & m8) & m8_16) ^ (Te0[(t3 >> 8U) & m8] & m8_8) ^ (Te1_r(t0 & m8) & m8) ^ rk[57]; + s2 = (Te2_r(t2 >> 24U) & m8_24) ^ (Te3_r((t3 >> 16U) & m8) & m8_16) ^ (Te0[(t0 >> 8U) & m8] & m8_8) ^ (Te1_r(t1 & m8) & m8) ^ rk[58]; + s3 = (Te2_r(t3 >> 24U) & m8_24) ^ (Te3_r((t0 >> 16U) & m8) & m8_16) ^ (Te0[(t1 >> 8U) & m8] & m8_8) ^ (Te1_r(t2 & m8) & m8) ^ rk[59]; + + out[0] = in[0] ^ (uint8_t)(s0 >> 24U); + out[1] = in[1] ^ (uint8_t)(s0 >> 16U); + out[2] = in[2] ^ (uint8_t)(s0 >> 8U); + out[3] = in[3] ^ (uint8_t)s0; + out[4] = in[4] ^ (uint8_t)(s1 >> 24U); + out[5] = in[5] ^ (uint8_t)(s1 >> 16U); + out[6] = in[6] ^ (uint8_t)(s1 >> 8U); + out[7] = in[7] ^ (uint8_t)s1; + out[8] = in[8] ^ (uint8_t)(s2 >> 24U); + out[9] = in[9] ^ (uint8_t)(s2 >> 16U); + out[10] = in[10] ^ (uint8_t)(s2 >> 8U); + out[11] = in[11] ^ (uint8_t)s2; + out[12] = in[12] ^ (uint8_t)(s3 >> 24U); + out[13] = in[13] ^ (uint8_t)(s3 >> 16U); + out[14] = in[14] ^ (uint8_t)(s3 >> 8U); + out[15] = in[15] ^ (uint8_t)s3; + out += 16; + in += 16; + } while ((len -= 16) >= 16); + } + reinterpret_cast(_ctr)[3] = Utils::hton(ctr); + } + + // Any remaining input is placed in _out. This will be picked up and crypted + // on subsequent calls to crypt() or finish() as it'll mean _len will not be + // an even multiple of 16. + while (len) { + --len; + *(out++) = *(in++); + } +} + +void AES::CTR::finish() noexcept +{ + uint8_t tmp[16]; + const unsigned int rem = _len & 15U; + if (rem) { + _aes.encrypt(_ctr, tmp); + for (unsigned int i = 0, j = _len - rem; i < rem; ++i) + _out[j + i] ^= tmp[i]; + } +} + +// Software AES and AES key expansion --------------------------------------------------------------------------------- + +const uint32_t AES::Te0[256] = {0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, + 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, + 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, + 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, + 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, + 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, + 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, + 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a}; +const uint32_t AES::Te4[256] = {0x63636363, 0x7c7c7c7c, 0x77777777, 0x7b7b7b7b, 0xf2f2f2f2, 0x6b6b6b6b, 0x6f6f6f6f, 0xc5c5c5c5, 0x30303030, 0x01010101, 0x67676767, 0x2b2b2b2b, 0xfefefefe, 0xd7d7d7d7, 0xabababab, 0x76767676, 0xcacacaca, 0x82828282, 0xc9c9c9c9, 0x7d7d7d7d, 0xfafafafa, 0x59595959, 0x47474747, 0xf0f0f0f0, 0xadadadad, 0xd4d4d4d4, 0xa2a2a2a2, 0xafafafaf, 0x9c9c9c9c, 0xa4a4a4a4, 0x72727272, 0xc0c0c0c0, 0xb7b7b7b7, 0xfdfdfdfd, 0x93939393, 0x26262626, 0x36363636, 0x3f3f3f3f, 0xf7f7f7f7, 0xcccccccc, 0x34343434, 0xa5a5a5a5, 0xe5e5e5e5, 0xf1f1f1f1, 0x71717171, 0xd8d8d8d8, 0x31313131, + 0x15151515, 0x04040404, 0xc7c7c7c7, 0x23232323, 0xc3c3c3c3, 0x18181818, 0x96969696, 0x05050505, 0x9a9a9a9a, 0x07070707, 0x12121212, 0x80808080, 0xe2e2e2e2, 0xebebebeb, 0x27272727, 0xb2b2b2b2, 0x75757575, + 0x09090909, 0x83838383, 0x2c2c2c2c, 0x1a1a1a1a, 0x1b1b1b1b, 0x6e6e6e6e, 0x5a5a5a5a, 0xa0a0a0a0, 0x52525252, 0x3b3b3b3b, 0xd6d6d6d6, 0xb3b3b3b3, 0x29292929, 0xe3e3e3e3, 0x2f2f2f2f, 0x84848484, 0x53535353, 0xd1d1d1d1, 0x00000000, 0xedededed, 0x20202020, 0xfcfcfcfc, 0xb1b1b1b1, 0x5b5b5b5b, 0x6a6a6a6a, 0xcbcbcbcb, 0xbebebebe, 0x39393939, 0x4a4a4a4a, 0x4c4c4c4c, 0x58585858, 0xcfcfcfcf, 0xd0d0d0d0, 0xefefefef, 0xaaaaaaaa, 0xfbfbfbfb, 0x43434343, 0x4d4d4d4d, 0x33333333, 0x85858585, 0x45454545, 0xf9f9f9f9, 0x02020202, 0x7f7f7f7f, 0x50505050, 0x3c3c3c3c, 0x9f9f9f9f, + 0xa8a8a8a8, 0x51515151, 0xa3a3a3a3, 0x40404040, 0x8f8f8f8f, 0x92929292, 0x9d9d9d9d, 0x38383838, 0xf5f5f5f5, 0xbcbcbcbc, 0xb6b6b6b6, 0xdadadada, 0x21212121, 0x10101010, 0xffffffff, 0xf3f3f3f3, 0xd2d2d2d2, + 0xcdcdcdcd, 0x0c0c0c0c, 0x13131313, 0xecececec, 0x5f5f5f5f, 0x97979797, 0x44444444, 0x17171717, 0xc4c4c4c4, 0xa7a7a7a7, 0x7e7e7e7e, 0x3d3d3d3d, 0x64646464, 0x5d5d5d5d, 0x19191919, 0x73737373, 0x60606060, 0x81818181, 0x4f4f4f4f, 0xdcdcdcdc, 0x22222222, 0x2a2a2a2a, 0x90909090, 0x88888888, 0x46464646, 0xeeeeeeee, 0xb8b8b8b8, 0x14141414, 0xdededede, 0x5e5e5e5e, 0x0b0b0b0b, 0xdbdbdbdb, 0xe0e0e0e0, 0x32323232, 0x3a3a3a3a, 0x0a0a0a0a, 0x49494949, 0x06060606, 0x24242424, 0x5c5c5c5c, 0xc2c2c2c2, 0xd3d3d3d3, 0xacacacac, 0x62626262, 0x91919191, 0x95959595, 0xe4e4e4e4, + 0x79797979, 0xe7e7e7e7, 0xc8c8c8c8, 0x37373737, 0x6d6d6d6d, 0x8d8d8d8d, 0xd5d5d5d5, 0x4e4e4e4e, 0xa9a9a9a9, 0x6c6c6c6c, 0x56565656, 0xf4f4f4f4, 0xeaeaeaea, 0x65656565, 0x7a7a7a7a, 0xaeaeaeae, 0x08080808, + 0xbabababa, 0x78787878, 0x25252525, 0x2e2e2e2e, 0x1c1c1c1c, 0xa6a6a6a6, 0xb4b4b4b4, 0xc6c6c6c6, 0xe8e8e8e8, 0xdddddddd, 0x74747474, 0x1f1f1f1f, 0x4b4b4b4b, 0xbdbdbdbd, 0x8b8b8b8b, 0x8a8a8a8a, 0x70707070, 0x3e3e3e3e, 0xb5b5b5b5, 0x66666666, 0x48484848, 0x03030303, 0xf6f6f6f6, 0x0e0e0e0e, 0x61616161, 0x35353535, 0x57575757, 0xb9b9b9b9, 0x86868686, 0xc1c1c1c1, 0x1d1d1d1d, 0x9e9e9e9e, 0xe1e1e1e1, 0xf8f8f8f8, 0x98989898, 0x11111111, 0x69696969, 0xd9d9d9d9, 0x8e8e8e8e, 0x94949494, 0x9b9b9b9b, 0x1e1e1e1e, 0x87878787, 0xe9e9e9e9, 0xcececece, 0x55555555, 0x28282828, + 0xdfdfdfdf, 0x8c8c8c8c, 0xa1a1a1a1, 0x89898989, 0x0d0d0d0d, 0xbfbfbfbf, 0xe6e6e6e6, 0x42424242, 0x68686868, 0x41414141, 0x99999999, 0x2d2d2d2d, 0x0f0f0f0f, 0xb0b0b0b0, 0x54545454, 0xbbbbbbbb, 0x16161616}; +const uint32_t AES::Td0[256] = {0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, + 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, + 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, + 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, + 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, + 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, + 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, + 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742}; +const uint8_t AES::Td4[256] = {0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, + 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, + 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d}; +const uint32_t AES::rcon[15] = {0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000, 0x6c000000, 0xd8000000, 0xab000000, 0x4d000000, 0x9a000000}; + +void AES::_initSW(const uint8_t key[32]) noexcept +{ + uint32_t *rk = _k.sw.ek; + + rk[0] = Utils::loadBigEndian< uint32_t >(key); + rk[1] = Utils::loadBigEndian< uint32_t >(key + 4); + rk[2] = Utils::loadBigEndian< uint32_t >(key + 8); + rk[3] = Utils::loadBigEndian< uint32_t >(key + 12); + rk[4] = Utils::loadBigEndian< uint32_t >(key + 16); + rk[5] = Utils::loadBigEndian< uint32_t >(key + 20); + rk[6] = Utils::loadBigEndian< uint32_t >(key + 24); + rk[7] = Utils::loadBigEndian< uint32_t >(key + 28); + for (int i = 0;;) { + uint32_t temp = rk[7]; + rk[8] = rk[0] ^ (Te2_r((temp >> 16U) & 0xffU) & 0xff000000U) ^ (Te3_r((temp >> 8U) & 0xffU) & 0x00ff0000U) ^ (Te0[(temp) & 0xffU] & 0x0000ff00U) ^ (Te1_r(temp >> 24U) & 0x000000ffU) ^ rcon[i]; + rk[9] = rk[1] ^ rk[8]; + rk[10] = rk[2] ^ rk[9]; + rk[11] = rk[3] ^ rk[10]; + if (++i == 7) + break; + temp = rk[11]; + rk[12] = rk[4] ^ (Te2_r(temp >> 24U) & 0xff000000U) ^ (Te3_r((temp >> 16U) & 0xffU) & 0x00ff0000U) ^ (Te0[(temp >> 8U) & 0xffU] & 0x0000ff00U) ^ (Te1_r((temp) & 0xffU) & 0x000000ffU); + rk[13] = rk[5] ^ rk[12]; + rk[14] = rk[6] ^ rk[13]; + rk[15] = rk[7] ^ rk[14]; + rk += 8; + } + + _encryptSW((const uint8_t *)Utils::ZERO256, (uint8_t *)_k.sw.h); + _k.sw.h[0] = Utils::ntoh(_k.sw.h[0]); + _k.sw.h[1] = Utils::ntoh(_k.sw.h[1]); + + for (int i = 0; i < 60; ++i) + _k.sw.dk[i] = _k.sw.ek[i]; + rk = _k.sw.dk; + + for (int i = 0, j = 56; i < j; i += 4, j -= 4) { + uint32_t temp = rk[i]; + rk[i] = rk[j]; + rk[j] = temp; + temp = rk[i + 1]; + rk[i + 1] = rk[j + 1]; + rk[j + 1] = temp; + temp = rk[i + 2]; + rk[i + 2] = rk[j + 2]; + rk[j + 2] = temp; + temp = rk[i + 3]; + rk[i + 3] = rk[j + 3]; + rk[j + 3] = temp; + } + for (int i = 1; i < 14; ++i) { + rk += 4; + rk[0] = Td0[Te4[(rk[0] >> 24U)] & 0xffU] ^ Td1_r(Te4[(rk[0] >> 16U) & 0xffU] & 0xffU) ^ Td2_r(Te4[(rk[0] >> 8U) & 0xffU] & 0xffU) ^ Td3_r(Te4[(rk[0]) & 0xffU] & 0xffU); + rk[1] = Td0[Te4[(rk[1] >> 24U)] & 0xffU] ^ Td1_r(Te4[(rk[1] >> 16U) & 0xffU] & 0xffU) ^ Td2_r(Te4[(rk[1] >> 8U) & 0xffU] & 0xffU) ^ Td3_r(Te4[(rk[1]) & 0xffU] & 0xffU); + rk[2] = Td0[Te4[(rk[2] >> 24U)] & 0xffU] ^ Td1_r(Te4[(rk[2] >> 16U) & 0xffU] & 0xffU) ^ Td2_r(Te4[(rk[2] >> 8U) & 0xffU] & 0xffU) ^ Td3_r(Te4[(rk[2]) & 0xffU] & 0xffU); + rk[3] = Td0[Te4[(rk[3] >> 24U)] & 0xffU] ^ Td1_r(Te4[(rk[3] >> 16U) & 0xffU] & 0xffU) ^ Td2_r(Te4[(rk[3] >> 8U) & 0xffU] & 0xffU) ^ Td3_r(Te4[(rk[3]) & 0xffU] & 0xffU); + } +} + +void AES::_encryptSW(const uint8_t in[16], uint8_t out[16]) const noexcept +{ + const uint32_t *const restrict rk = _k.sw.ek; + const uint32_t m8 = 0x000000ff; + const uint32_t m8_8 = 0x0000ff00; + const uint32_t m8_16 = 0x00ff0000; + const uint32_t m8_24 = 0xff000000; + uint32_t s0 = Utils::loadBigEndian< uint32_t >(in) ^rk[0]; + uint32_t s1 = Utils::loadBigEndian< uint32_t >(in + 4) ^rk[1]; + uint32_t s2 = Utils::loadBigEndian< uint32_t >(in + 8) ^rk[2]; + uint32_t s3 = Utils::loadBigEndian< uint32_t >(in + 12) ^rk[3]; + + uint32_t t0, t1, t2, t3; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[4]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[5]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[6]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[7]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[8]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[9]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[10]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[11]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[12]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[13]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[14]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[15]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[16]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[17]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[18]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[19]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[20]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[21]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[22]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[23]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[24]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[25]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[26]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[27]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[28]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[29]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[30]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[31]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[32]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[33]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[34]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[35]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[36]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[37]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[38]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[39]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[40]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[41]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[42]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[43]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[44]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[45]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[46]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[47]; + s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[48]; + s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[49]; + s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[50]; + s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[51]; + t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[52]; + t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[53]; + t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[54]; + t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[55]; + s0 = (Te2_r(t0 >> 24U) & m8_24) ^ (Te3_r((t1 >> 16U) & m8) & m8_16) ^ (Te0[(t2 >> 8U) & m8] & m8_8) ^ (Te1_r(t3 & m8) & m8) ^ rk[56]; + s1 = (Te2_r(t1 >> 24U) & m8_24) ^ (Te3_r((t2 >> 16U) & m8) & m8_16) ^ (Te0[(t3 >> 8U) & m8] & m8_8) ^ (Te1_r(t0 & m8) & m8) ^ rk[57]; + s2 = (Te2_r(t2 >> 24U) & m8_24) ^ (Te3_r((t3 >> 16U) & m8) & m8_16) ^ (Te0[(t0 >> 8U) & m8] & m8_8) ^ (Te1_r(t1 & m8) & m8) ^ rk[58]; + s3 = (Te2_r(t3 >> 24U) & m8_24) ^ (Te3_r((t0 >> 16U) & m8) & m8_16) ^ (Te0[(t1 >> 8U) & m8] & m8_8) ^ (Te1_r(t2 & m8) & m8) ^ rk[59]; + + Utils::storeBigEndian< uint32_t >(out, s0); + Utils::storeBigEndian< uint32_t >(out + 4, s1); + Utils::storeBigEndian< uint32_t >(out + 8, s2); + Utils::storeBigEndian< uint32_t >(out + 12, s3); +} + +void AES::_decryptSW(const uint8_t in[16], uint8_t out[16]) const noexcept +{ + const uint32_t *restrict rk = _k.sw.dk; + const uint32_t m8 = 0x000000ff; + uint32_t s0 = Utils::loadBigEndian< uint32_t >(in) ^rk[0]; + uint32_t s1 = Utils::loadBigEndian< uint32_t >(in + 4) ^rk[1]; + uint32_t s2 = Utils::loadBigEndian< uint32_t >(in + 8) ^rk[2]; + uint32_t s3 = Utils::loadBigEndian< uint32_t >(in + 12) ^rk[3]; + + uint32_t t0, t1, t2, t3; + t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[4]; + t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[5]; + t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[6]; + t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[7]; + s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[8]; + s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[9]; + s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[10]; + s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[11]; + t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[12]; + t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[13]; + t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[14]; + t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[15]; + s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[16]; + s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[17]; + s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[18]; + s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[19]; + t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[20]; + t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[21]; + t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[22]; + t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[23]; + s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[24]; + s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[25]; + s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[26]; + s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[27]; + t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[28]; + t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[29]; + t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[30]; + t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[31]; + s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[32]; + s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[33]; + s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[34]; + s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[35]; + t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[36]; + t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[37]; + t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[38]; + t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[39]; + s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[40]; + s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[41]; + s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[42]; + s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[43]; + t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[44]; + t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[45]; + t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[46]; + t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[47]; + s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[48]; + s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[49]; + s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[50]; + s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[51]; + t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[52]; + t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[53]; + t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[54]; + t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[55]; + s0 = (Td4[t0 >> 24U] << 24U) ^ (Td4[(t3 >> 16U) & m8] << 16U) ^ (Td4[(t2 >> 8U) & m8] << 8U) ^ (Td4[(t1) & m8]) ^ rk[56]; + s1 = (Td4[t1 >> 24U] << 24U) ^ (Td4[(t0 >> 16U) & m8] << 16U) ^ (Td4[(t3 >> 8U) & m8] << 8U) ^ (Td4[(t2) & m8]) ^ rk[57]; + s2 = (Td4[t2 >> 24U] << 24U) ^ (Td4[(t1 >> 16U) & m8] << 16U) ^ (Td4[(t0 >> 8U) & m8] << 8U) ^ (Td4[(t3) & m8]) ^ rk[58]; + s3 = (Td4[t3 >> 24U] << 24U) ^ (Td4[(t2 >> 16U) & m8] << 16U) ^ (Td4[(t1 >> 8U) & m8] << 8U) ^ (Td4[(t0) & m8]) ^ rk[59]; + + Utils::storeBigEndian< uint32_t >(out, s0); + Utils::storeBigEndian< uint32_t >(out + 4, s1); + Utils::storeBigEndian< uint32_t >(out + 8, s2); + Utils::storeBigEndian< uint32_t >(out + 12, s3); +} + +#ifdef ZT_AES_AESNI + +static __m128i _init256_1_aesni(__m128i a, __m128i b) noexcept +{ + __m128i x, y; + b = _mm_shuffle_epi32(b, 0xff); + y = _mm_slli_si128(a, 0x04); + x = _mm_xor_si128(a, y); + y = _mm_slli_si128(y, 0x04); + x = _mm_xor_si128(x, y); + y = _mm_slli_si128(y, 0x04); + x = _mm_xor_si128(x, y); + x = _mm_xor_si128(x, b); + return x; +} + +static __m128i _init256_2_aesni(__m128i a, __m128i b) noexcept +{ + __m128i x, y, z; + y = _mm_aeskeygenassist_si128(a, 0x00); + z = _mm_shuffle_epi32(y, 0xaa); + y = _mm_slli_si128(b, 0x04); + x = _mm_xor_si128(b, y); + y = _mm_slli_si128(y, 0x04); + x = _mm_xor_si128(x, y); + y = _mm_slli_si128(y, 0x04); + x = _mm_xor_si128(x, y); + x = _mm_xor_si128(x, z); + return x; +} + +void AES::_init_aesni(const uint8_t key[32]) noexcept +{ + __m128i t1, t2, k1, k2, k3, k4, k5, k6, k7, k8, k9, k10, k11, k12, k13; + _k.ni.k[0] = t1 = _mm_loadu_si128((const __m128i *)key); + _k.ni.k[1] = k1 = t2 = _mm_loadu_si128((const __m128i *)(key + 16)); + _k.ni.k[2] = k2 = t1 = _init256_1_aesni(t1, _mm_aeskeygenassist_si128(t2, 0x01)); + _k.ni.k[3] = k3 = t2 = _init256_2_aesni(t1, t2); + _k.ni.k[4] = k4 = t1 = _init256_1_aesni(t1, _mm_aeskeygenassist_si128(t2, 0x02)); + _k.ni.k[5] = k5 = t2 = _init256_2_aesni(t1, t2); + _k.ni.k[6] = k6 = t1 = _init256_1_aesni(t1, _mm_aeskeygenassist_si128(t2, 0x04)); + _k.ni.k[7] = k7 = t2 = _init256_2_aesni(t1, t2); + _k.ni.k[8] = k8 = t1 = _init256_1_aesni(t1, _mm_aeskeygenassist_si128(t2, 0x08)); + _k.ni.k[9] = k9 = t2 = _init256_2_aesni(t1, t2); + _k.ni.k[10] = k10 = t1 = _init256_1_aesni(t1, _mm_aeskeygenassist_si128(t2, 0x10)); + _k.ni.k[11] = k11 = t2 = _init256_2_aesni(t1, t2); + _k.ni.k[12] = k12 = t1 = _init256_1_aesni(t1, _mm_aeskeygenassist_si128(t2, 0x20)); + _k.ni.k[13] = k13 = t2 = _init256_2_aesni(t1, t2); + _k.ni.k[14] = _init256_1_aesni(t1, _mm_aeskeygenassist_si128(t2, 0x40)); + _k.ni.k[15] = _mm_aesimc_si128(k13); + _k.ni.k[16] = _mm_aesimc_si128(k12); + _k.ni.k[17] = _mm_aesimc_si128(k11); + _k.ni.k[18] = _mm_aesimc_si128(k10); + _k.ni.k[19] = _mm_aesimc_si128(k9); + _k.ni.k[20] = _mm_aesimc_si128(k8); + _k.ni.k[21] = _mm_aesimc_si128(k7); + _k.ni.k[22] = _mm_aesimc_si128(k6); + _k.ni.k[23] = _mm_aesimc_si128(k5); + _k.ni.k[24] = _mm_aesimc_si128(k4); + _k.ni.k[25] = _mm_aesimc_si128(k3); + _k.ni.k[26] = _mm_aesimc_si128(k2); + _k.ni.k[27] = _mm_aesimc_si128(k1); + + __m128i h = _k.ni.k[0]; // _mm_xor_si128(_mm_setzero_si128(),_k.ni.k[0]); + h = _mm_aesenc_si128(h, k1); + h = _mm_aesenc_si128(h, k2); + h = _mm_aesenc_si128(h, k3); + h = _mm_aesenc_si128(h, k4); + h = _mm_aesenc_si128(h, k5); + h = _mm_aesenc_si128(h, k6); + h = _mm_aesenc_si128(h, k7); + h = _mm_aesenc_si128(h, k8); + h = _mm_aesenc_si128(h, k9); + h = _mm_aesenc_si128(h, k10); + h = _mm_aesenc_si128(h, k11); + h = _mm_aesenc_si128(h, k12); + h = _mm_aesenc_si128(h, k13); + h = _mm_aesenclast_si128(h, _k.ni.k[14]); + __m128i hswap = _mm_shuffle_epi8(h, s_sseSwapBytes); + __m128i hh = p_gmacPCLMUL128(hswap, h); + __m128i hhh = p_gmacPCLMUL128(hswap, hh); + __m128i hhhh = p_gmacPCLMUL128(hswap, hhh); + _k.ni.h[0] = hswap; + _k.ni.h[1] = hh = _mm_shuffle_epi8(hh, s_sseSwapBytes); + _k.ni.h[2] = hhh = _mm_shuffle_epi8(hhh, s_sseSwapBytes); + _k.ni.h[3] = hhhh = _mm_shuffle_epi8(hhhh, s_sseSwapBytes); + _k.ni.h2[0] = _mm_xor_si128(_mm_shuffle_epi32(hswap, 78), hswap); + _k.ni.h2[1] = _mm_xor_si128(_mm_shuffle_epi32(hh, 78), hh); + _k.ni.h2[2] = _mm_xor_si128(_mm_shuffle_epi32(hhh, 78), hhh); + _k.ni.h2[3] = _mm_xor_si128(_mm_shuffle_epi32(hhhh, 78), hhhh); +} + +void AES::_encrypt_aesni(const void *const in, void *const out) const noexcept +{ + __m128i tmp = _mm_loadu_si128((const __m128i *)in); + tmp = _mm_xor_si128(tmp, _k.ni.k[0]); + tmp = _mm_aesenc_si128(tmp, _k.ni.k[1]); + tmp = _mm_aesenc_si128(tmp, _k.ni.k[2]); + tmp = _mm_aesenc_si128(tmp, _k.ni.k[3]); + tmp = _mm_aesenc_si128(tmp, _k.ni.k[4]); + tmp = _mm_aesenc_si128(tmp, _k.ni.k[5]); + tmp = _mm_aesenc_si128(tmp, _k.ni.k[6]); + tmp = _mm_aesenc_si128(tmp, _k.ni.k[7]); + tmp = _mm_aesenc_si128(tmp, _k.ni.k[8]); + tmp = _mm_aesenc_si128(tmp, _k.ni.k[9]); + tmp = _mm_aesenc_si128(tmp, _k.ni.k[10]); + tmp = _mm_aesenc_si128(tmp, _k.ni.k[11]); + tmp = _mm_aesenc_si128(tmp, _k.ni.k[12]); + tmp = _mm_aesenc_si128(tmp, _k.ni.k[13]); + _mm_storeu_si128((__m128i *)out, _mm_aesenclast_si128(tmp, _k.ni.k[14])); +} + +void AES::_decrypt_aesni(const void *in, void *out) const noexcept +{ + __m128i tmp = _mm_loadu_si128((const __m128i *)in); + tmp = _mm_xor_si128(tmp, _k.ni.k[14]); + tmp = _mm_aesdec_si128(tmp, _k.ni.k[15]); + tmp = _mm_aesdec_si128(tmp, _k.ni.k[16]); + tmp = _mm_aesdec_si128(tmp, _k.ni.k[17]); + tmp = _mm_aesdec_si128(tmp, _k.ni.k[18]); + tmp = _mm_aesdec_si128(tmp, _k.ni.k[19]); + tmp = _mm_aesdec_si128(tmp, _k.ni.k[20]); + tmp = _mm_aesdec_si128(tmp, _k.ni.k[21]); + tmp = _mm_aesdec_si128(tmp, _k.ni.k[22]); + tmp = _mm_aesdec_si128(tmp, _k.ni.k[23]); + tmp = _mm_aesdec_si128(tmp, _k.ni.k[24]); + tmp = _mm_aesdec_si128(tmp, _k.ni.k[25]); + tmp = _mm_aesdec_si128(tmp, _k.ni.k[26]); + tmp = _mm_aesdec_si128(tmp, _k.ni.k[27]); + _mm_storeu_si128((__m128i *)out, _mm_aesdeclast_si128(tmp, _k.ni.k[0])); +} + +#endif // ZT_AES_AESNI + +#ifdef ZT_AES_NEON + +#define ZT_INIT_ARMNEON_CRYPTO_SUBWORD(w) ((uint32_t)s_sbox[w & 0xffU] + ((uint32_t)s_sbox[(w >> 8U) & 0xffU] << 8U) + ((uint32_t)s_sbox[(w >> 16U) & 0xffU] << 16U) + ((uint32_t)s_sbox[(w >> 24U) & 0xffU] << 24U)) +#define ZT_INIT_ARMNEON_CRYPTO_ROTWORD(w) (((w) << 8U) | ((w) >> 24U)) +#define ZT_INIT_ARMNEON_CRYPTO_NK 8 +#define ZT_INIT_ARMNEON_CRYPTO_NB 4 +#define ZT_INIT_ARMNEON_CRYPTO_NR 14 + +void AES::_init_armneon_crypto(const uint8_t key[32]) noexcept +{ + static const uint8_t s_sbox[256] = {0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, + 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, + 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16}; + + uint64_t h[2]; + uint32_t *const w = reinterpret_cast(_k.neon.ek); + + for (unsigned int i=0;i(&(_k.neon.h), h); + _k.neon.h = vrbitq_u8(_k.neon.h); + _k.sw.h[0] = Utils::ntoh(h[0]); + _k.sw.h[1] = Utils::ntoh(h[1]); +} + +void AES::_encrypt_armneon_crypto(const void *const in, void *const out) const noexcept +{ + uint8x16_t tmp = vld1q_u8(reinterpret_cast(in)); + tmp = vaesmcq_u8(vaeseq_u8(tmp, _k.neon.ek[0])); + tmp = vaesmcq_u8(vaeseq_u8(tmp, _k.neon.ek[1])); + tmp = vaesmcq_u8(vaeseq_u8(tmp, _k.neon.ek[2])); + tmp = vaesmcq_u8(vaeseq_u8(tmp, _k.neon.ek[3])); + tmp = vaesmcq_u8(vaeseq_u8(tmp, _k.neon.ek[4])); + tmp = vaesmcq_u8(vaeseq_u8(tmp, _k.neon.ek[5])); + tmp = vaesmcq_u8(vaeseq_u8(tmp, _k.neon.ek[6])); + tmp = vaesmcq_u8(vaeseq_u8(tmp, _k.neon.ek[7])); + tmp = vaesmcq_u8(vaeseq_u8(tmp, _k.neon.ek[8])); + tmp = vaesmcq_u8(vaeseq_u8(tmp, _k.neon.ek[9])); + tmp = vaesmcq_u8(vaeseq_u8(tmp, _k.neon.ek[10])); + tmp = vaesmcq_u8(vaeseq_u8(tmp, _k.neon.ek[11])); + tmp = vaesmcq_u8(vaeseq_u8(tmp, _k.neon.ek[12])); + tmp = veorq_u8(vaeseq_u8(tmp, _k.neon.ek[13]), _k.neon.ek[14]); + vst1q_u8(reinterpret_cast(out), tmp); +} + +void AES::_decrypt_armneon_crypto(const void *const in, void *const out) const noexcept +{ + uint8x16_t tmp = vld1q_u8(reinterpret_cast(in)); + tmp = vaesimcq_u8(vaesdq_u8(tmp, _k.neon.dk[0])); + tmp = vaesimcq_u8(vaesdq_u8(tmp, _k.neon.dk[1])); + tmp = vaesimcq_u8(vaesdq_u8(tmp, _k.neon.dk[2])); + tmp = vaesimcq_u8(vaesdq_u8(tmp, _k.neon.dk[3])); + tmp = vaesimcq_u8(vaesdq_u8(tmp, _k.neon.dk[4])); + tmp = vaesimcq_u8(vaesdq_u8(tmp, _k.neon.dk[5])); + tmp = vaesimcq_u8(vaesdq_u8(tmp, _k.neon.dk[6])); + tmp = vaesimcq_u8(vaesdq_u8(tmp, _k.neon.dk[7])); + tmp = vaesimcq_u8(vaesdq_u8(tmp, _k.neon.dk[8])); + tmp = vaesimcq_u8(vaesdq_u8(tmp, _k.neon.dk[9])); + tmp = vaesimcq_u8(vaesdq_u8(tmp, _k.neon.dk[10])); + tmp = vaesimcq_u8(vaesdq_u8(tmp, _k.neon.dk[11])); + tmp = vaesimcq_u8(vaesdq_u8(tmp, _k.neon.dk[12])); + tmp = veorq_u8(vaesdq_u8(tmp, _k.neon.dk[13]), _k.neon.dk[14]); + vst1q_u8(reinterpret_cast(out), tmp); +} + +#endif // ZT_AES_NEON + +} // namespace ZeroTier diff --git a/node/AES.hpp b/node/AES.hpp new file mode 100644 index 000000000..5b639ce47 --- /dev/null +++ b/node/AES.hpp @@ -0,0 +1,580 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2025-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +#ifndef ZT_AES_HPP +#define ZT_AES_HPP + +#include "Constants.hpp" +#include "Utils.hpp" +#include "SHA512.hpp" + +//#define ZT_AES_NO_ACCEL + +#if !defined(ZT_AES_NO_ACCEL) && defined(ZT_ARCH_X64) +#define ZT_AES_AESNI 1 +#endif +#if !defined(ZT_AES_NO_ACCEL) && defined(ZT_ARCH_ARM_HAS_NEON) +#define ZT_AES_NEON 1 +#endif + +#ifndef ZT_INLINE +#define ZT_INLINE inline +#endif + +namespace ZeroTier { + +/** + * AES-256 and pals including GMAC, CTR, etc. + * + * This includes hardware acceleration for certain processors. The software + * mode is fallback and is significantly slower. + */ +class AES +{ +public: + /** + * @return True if this system has hardware AES acceleration + */ + static ZT_INLINE bool accelerated() + { +#ifdef ZT_AES_AESNI + return Utils::CPUID.aes; +#else +#ifdef ZT_AES_NEON + return Utils::ARMCAP.aes; +#else + return false; +#endif +#endif + } + + /** + * Create an un-initialized AES instance (must call init() before use) + */ + ZT_INLINE AES() noexcept + {} + + /** + * Create an AES instance with the given key + * + * @param key 256-bit key + */ + explicit ZT_INLINE AES(const void *const key) noexcept + { this->init(key); } + + ZT_INLINE ~AES() + { Utils::burn(&_k, sizeof(_k)); } + + /** + * Set (or re-set) this AES256 cipher's key + * + * @param key 256-bit / 32-byte key + */ + ZT_INLINE void init(const void *const key) noexcept + { +#ifdef ZT_AES_AESNI + if (likely(Utils::CPUID.aes)) { + _init_aesni(reinterpret_cast(key)); + return; + } +#endif +#ifdef ZT_AES_NEON + if (Utils::ARMCAP.aes) { + _init_armneon_crypto(reinterpret_cast(key)); + return; + } +#endif + _initSW(reinterpret_cast(key)); + } + + /** + * Encrypt a single AES block + * + * @param in Input block + * @param out Output block (can be same as input) + */ + ZT_INLINE void encrypt(const void *const in, void *const out) const noexcept + { +#ifdef ZT_AES_AESNI + if (likely(Utils::CPUID.aes)) { + _encrypt_aesni(in, out); + return; + } +#endif +#ifdef ZT_AES_NEON + if (Utils::ARMCAP.aes) { + _encrypt_armneon_crypto(in, out); + return; + } +#endif + _encryptSW(reinterpret_cast(in), reinterpret_cast(out)); + } + + /** + * Decrypt a single AES block + * + * @param in Input block + * @param out Output block (can be same as input) + */ + ZT_INLINE void decrypt(const void *const in, void *const out) const noexcept + { +#ifdef ZT_AES_AESNI + if (likely(Utils::CPUID.aes)) { + _decrypt_aesni(in, out); + return; + } +#endif +#ifdef ZT_AES_NEON + if (Utils::ARMCAP.aes) { + _decrypt_armneon_crypto(in, out); + return; + } +#endif + _decryptSW(reinterpret_cast(in), reinterpret_cast(out)); + } + + class GMACSIVEncryptor; + class GMACSIVDecryptor; + + /** + * Streaming GMAC calculator + */ + class GMAC + { + friend class GMACSIVEncryptor; + friend class GMACSIVDecryptor; + + public: + /** + * @return True if this system has hardware GMAC acceleration + */ + static ZT_INLINE bool accelerated() + { +#ifdef ZT_AES_AESNI + return Utils::CPUID.aes; +#else +#ifdef ZT_AES_NEON + return Utils::ARMCAP.pmull; +#else + return false; +#endif +#endif + } + + /** + * Create a new instance of GMAC (must be initialized with init() before use) + * + * @param aes Keyed AES instance to use + */ + ZT_INLINE GMAC(const AES &aes) : _aes(aes) + {} + + /** + * Reset and initialize for a new GMAC calculation + * + * @param iv 96-bit initialization vector (pad with zeroes if actual IV is shorter) + */ + ZT_INLINE void init(const uint8_t iv[12]) noexcept + { + _rp = 0; + _len = 0; + // We fill the least significant 32 bits in the _iv field with 1 since in GCM mode + // this would hold the counter, but we're not doing GCM. The counter is therefore + // always 1. +#ifdef ZT_AES_AESNI // also implies an x64 processor + *reinterpret_cast(_iv) = *reinterpret_cast(iv); + *reinterpret_cast(_iv + 8) = *reinterpret_cast(iv + 8); + *reinterpret_cast(_iv + 12) = 0x01000000; // 0x00000001 in big-endian byte order +#else + for(int i=0;i<12;++i) + _iv[i] = iv[i]; + _iv[12] = 0; + _iv[13] = 0; + _iv[14] = 0; + _iv[15] = 1; +#endif + _y[0] = 0; + _y[1] = 0; + } + + /** + * Process data through GMAC + * + * @param data Bytes to process + * @param len Length of input + */ + void update(const void *data, unsigned int len) noexcept; + + /** + * Process any remaining cached bytes and generate tag + * + * Don't call finish() more than once or you'll get an invalid result. + * + * @param tag 128-bit GMAC tag (can be truncated) + */ + void finish(uint8_t tag[16]) noexcept; + + private: + const AES &_aes; + unsigned int _rp; + unsigned int _len; + uint8_t _r[16]; // remainder + uint8_t _iv[16]; + uint64_t _y[2]; + }; + + /** + * Streaming AES-CTR encrypt/decrypt + * + * NOTE: this doesn't support overflow of the counter in the least significant 32 bits. + * AES-GMAC-CTR doesn't need this, so we don't support it as an optimization. + */ + class CTR + { + friend class GMACSIVEncryptor; + friend class GMACSIVDecryptor; + + public: + ZT_INLINE CTR(const AES &aes) noexcept: _aes(aes) + {} + + /** + * Initialize this CTR instance to encrypt a new stream + * + * @param iv Unique initialization vector and initial 32-bit counter (least significant 32 bits, big-endian) + * @param output Buffer to which to store output (MUST be large enough for total bytes processed!) + */ + ZT_INLINE void init(const uint8_t iv[16], void *const output) noexcept + { + Utils::copy< 16 >(_ctr, iv); + _out = reinterpret_cast(output); + _len = 0; + } + + /** + * Initialize this CTR instance to encrypt a new stream + * + * @param iv Unique initialization vector + * @param ic Initial counter (must be in big-endian byte order!) + * @param output Buffer to which to store output (MUST be large enough for total bytes processed!) + */ + ZT_INLINE void init(const uint8_t iv[12], const uint32_t ic, void *const output) noexcept + { + Utils::copy< 12 >(_ctr, iv); + reinterpret_cast(_ctr)[3] = ic; + _out = reinterpret_cast(output); + _len = 0; + } + + /** + * Encrypt or decrypt data, writing result to the output provided to init() + * + * @param input Input data + * @param len Length of input + */ + void crypt(const void *input, unsigned int len) noexcept; + + /** + * Finish any remaining bytes if total bytes processed wasn't a multiple of 16 + * + * Don't call more than once for a given stream or data may be corrupted. + */ + void finish() noexcept; + + private: + const AES &_aes; + uint64_t _ctr[2]; + uint8_t *_out; + unsigned int _len; + }; + + /** + * Encryptor for AES-GMAC-SIV. + * + * Encryption requires two passes. The first pass starts after init + * with aad (if any) followed by update1() and finish1(). Then the + * update2() and finish2() methods must be used over the same data + * (but NOT AAD) again. + * + * This supports encryption of a maximum of 2^31 bytes of data per + * call to init(). + */ + class GMACSIVEncryptor + { + public: + /** + * Create a new AES-GMAC-SIV encryptor keyed with the provided AES instances + * + * @param k0 First of two AES instances keyed with K0 + * @param k1 Second of two AES instances keyed with K1 + */ + ZT_INLINE GMACSIVEncryptor(const AES &k0, const AES &k1) noexcept: + _gmac(k0), + _ctr(k1) + {} + + /** + * Initialize AES-GMAC-SIV + * + * @param iv IV in network byte order (byte order in which it will appear on the wire) + * @param output Pointer to buffer to receive ciphertext, must be large enough for all to-be-processed data! + */ + ZT_INLINE void init(const uint64_t iv, void *const output) noexcept + { + // Output buffer to receive the result of AES-CTR encryption. + _output = output; + + // Initialize GMAC with 64-bit IV (and remaining 32 bits padded to zero). + _tag[0] = iv; + _tag[1] = 0; + _gmac.init(reinterpret_cast(_tag)); + } + + /** + * Process AAD (additional authenticated data) that is not being encrypted. + * + * If such data exists this must be called before update1() and finish1(). + * + * Note: current code only supports one single chunk of AAD. Don't call this + * multiple times per message. + * + * @param aad Additional authenticated data + * @param len Length of AAD in bytes + */ + ZT_INLINE void aad(const void *const aad, unsigned int len) noexcept + { + // Feed ADD into GMAC first + _gmac.update(aad, len); + + // End of AAD is padded to a multiple of 16 bytes to ensure unique encoding. + len &= 0xfU; + if (len != 0) + _gmac.update(Utils::ZERO256, 16 - len); + } + + /** + * First pass plaintext input function + * + * @param input Plaintext chunk + * @param len Length of plaintext chunk + */ + ZT_INLINE void update1(const void *const input, const unsigned int len) noexcept + { _gmac.update(input, len); } + + /** + * Finish first pass, compute CTR IV, initialize second pass. + */ + ZT_INLINE void finish1() noexcept + { + uint64_t tmp[2]; + + // Compute 128-bit GMAC tag. + _gmac.finish(reinterpret_cast(tmp)); + + // Shorten to 64 bits, concatenate with message IV, and encrypt with AES to + // yield the CTR IV and opaque IV/MAC blob. In ZeroTier's use of GMAC-SIV + // this get split into the packet ID (64 bits) and the MAC (64 bits) in each + // packet and then recombined on receipt for legacy reasons (but with no + // cryptographic or performance impact). + _tag[1] = tmp[0] ^ tmp[1]; + _ctr._aes.encrypt(_tag, _tag); + + // Initialize CTR with 96-bit CTR nonce and 32-bit counter. The counter + // incorporates 31 more bits of entropy which should raise our security margin + // a bit, but this is not included in the worst case analysis of GMAC-SIV. + // The most significant bit of the counter is masked to zero to allow up to + // 2^31 bytes to be encrypted before the counter loops. Some CTR implementations + // increment the whole big-endian 128-bit integer in which case this could be + // used for more than 2^31 bytes, but ours does not for performance reasons + // and so 2^31 should be considered the input limit. + tmp[0] = _tag[0]; + tmp[1] = _tag[1] & ZT_CONST_TO_BE_UINT64(0xffffffff7fffffffULL); + _ctr.init(reinterpret_cast(tmp), _output); + } + + /** + * Second pass plaintext input function + * + * The same plaintext must be fed in the second time in the same order, + * though chunk boundaries do not have to be the same. + * + * @param input Plaintext chunk + * @param len Length of plaintext chunk + */ + ZT_INLINE void update2(const void *const input, const unsigned int len) noexcept + { _ctr.crypt(input, len); } + + /** + * Finish second pass and return a pointer to the opaque 128-bit IV+MAC block + * + * The returned pointer remains valid as long as this object exists and init() + * is not called again. + * + * @return Pointer to 128-bit opaque IV+MAC (packed into two 64-bit integers) + */ + ZT_INLINE const uint64_t *finish2() + { + _ctr.finish(); + return _tag; + } + + private: + void *_output; + uint64_t _tag[2]; + AES::GMAC _gmac; + AES::CTR _ctr; + }; + + /** + * Decryptor for AES-GMAC-SIV. + * + * GMAC-SIV decryption is single-pass. AAD (if any) must be processed first. + */ + class GMACSIVDecryptor + { + public: + ZT_INLINE GMACSIVDecryptor(const AES &k0, const AES &k1) noexcept: + _ctr(k1), + _gmac(k0) + {} + + /** + * Initialize decryptor for a new message + * + * @param tag 128-bit combined IV/MAC originally created by GMAC-SIV encryption + * @param output Buffer in which to write output plaintext (must be large enough!) + */ + ZT_INLINE void init(const uint64_t tag[2], void *const output) noexcept + { + uint64_t tmp[2]; + tmp[0] = tag[0]; + tmp[1] = tag[1] & ZT_CONST_TO_BE_UINT64(0xffffffff7fffffffULL); + _ctr.init(reinterpret_cast(tmp), output); + + _ctr._aes.decrypt(tag, _ivMac); + + tmp[0] = _ivMac[0]; + tmp[1] = 0; + _gmac.init(reinterpret_cast(tmp)); + + _output = output; + _decryptedLen = 0; + } + + /** + * Process AAD (additional authenticated data) that wasn't encrypted + * + * @param aad Additional authenticated data + * @param len Length of AAD in bytes + */ + ZT_INLINE void aad(const void *const aad, unsigned int len) noexcept + { + _gmac.update(aad, len); + len &= 0xfU; + if (len != 0) + _gmac.update(Utils::ZERO256, 16 - len); + } + + /** + * Feed ciphertext into the decryptor + * + * Unlike encryption, GMAC-SIV decryption requires only one pass. + * + * @param input Input ciphertext + * @param len Length of ciphertext + */ + ZT_INLINE void update(const void *const input, const unsigned int len) noexcept + { + _ctr.crypt(input, len); + _decryptedLen += len; + } + + /** + * Flush decryption, compute MAC, and verify + * + * @return True if resulting plaintext (and AAD) pass message authentication check + */ + ZT_INLINE bool finish() noexcept + { + _ctr.finish(); + + uint64_t gmacTag[2]; + _gmac.update(_output, _decryptedLen); + _gmac.finish(reinterpret_cast(gmacTag)); + return (gmacTag[0] ^ gmacTag[1]) == _ivMac[1]; + } + + private: + uint64_t _ivMac[2]; + AES::CTR _ctr; + AES::GMAC _gmac; + void *_output; + unsigned int _decryptedLen; + }; + +private: + static const uint32_t Te0[256]; + static const uint32_t Te4[256]; + static const uint32_t Td0[256]; + static const uint8_t Td4[256]; + static const uint32_t rcon[15]; + + void _initSW(const uint8_t key[32]) noexcept; + void _encryptSW(const uint8_t in[16], uint8_t out[16]) const noexcept; + void _decryptSW(const uint8_t in[16], uint8_t out[16]) const noexcept; + + union + { +#ifdef ZT_AES_AESNI + struct + { + __m128i k[28]; + __m128i h[4]; // h, hh, hhh, hhhh + __m128i h2[4]; // _mm_xor_si128(_mm_shuffle_epi32(h, 78), h), etc. + } ni; +#endif + +#ifdef ZT_AES_NEON + struct + { + uint64_t hsw[2]; // in case it has AES but not PMULL, not sure if that ever happens + uint8x16_t ek[15]; + uint8x16_t dk[15]; + uint8x16_t h; + } neon; +#endif + + struct + { + uint64_t h[2]; + uint32_t ek[60]; + uint32_t dk[60]; + } sw; + } _k; + +#ifdef ZT_AES_AESNI + void _init_aesni(const uint8_t key[32]) noexcept; + void _encrypt_aesni(const void *in, void *out) const noexcept; + void _decrypt_aesni(const void *in, void *out) const noexcept; +#endif + +#ifdef ZT_AES_NEON + void _init_armneon_crypto(const uint8_t key[32]) noexcept; + void _encrypt_armneon_crypto(const void *in, void *out) const noexcept; + void _decrypt_armneon_crypto(const void *in, void *out) const noexcept; +#endif +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Constants.hpp b/node/Constants.hpp index 3f971ce4d..d9aebe65b 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -29,6 +29,12 @@ // Also makes sure __BYTE_ORDER is defined reasonably. // +#ifndef ZT_INLINE +#define ZT_INLINE inline +#endif + +#define restrict + // Hack: make sure __GCC__ is defined on old GCC compilers #ifndef __GCC__ #if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1) || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2) || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) @@ -96,6 +102,15 @@ #endif #endif +#if (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__) || defined(_M_X64)) +#define ZT_ARCH_X64 1 +#include +#include +#include +#include +#include +#endif + // Define ZT_NO_TYPE_PUNNING to disable reckless casts on anything other than x86/x64. #if (!(defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_AMD64) || defined(_M_X64) || defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || defined(_X86_) || defined(__I86__) || defined(__INTEL__) || defined(__386))) #ifndef ZT_NO_TYPE_PUNNING diff --git a/node/Utils.cpp b/node/Utils.cpp index 10da66767..7e3617c95 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -40,8 +40,87 @@ namespace ZeroTier { +const uint64_t Utils::ZERO256[4] = {0ULL,0ULL,0ULL,0ULL}; + const char Utils::HEXCHARS[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' }; +#ifdef ZT_ARCH_ARM_HAS_NEON +Utils::ARMCapabilities::ARMCapabilities() noexcept +{ +#ifdef HWCAP2_AES + if (sizeof(void *) == 4) { + const long hwcaps2 = getauxval(AT_HWCAP2); + this->aes = (hwcaps2 & HWCAP2_AES) != 0; + this->crc32 = (hwcaps2 & HWCAP2_CRC32) != 0; + this->pmull = (hwcaps2 & HWCAP2_PMULL) != 0; + this->sha1 = (hwcaps2 & HWCAP2_SHA1) != 0; + this->sha2 = (hwcaps2 & HWCAP2_SHA2) != 0; + } else { +#endif + const long hwcaps = getauxval(AT_HWCAP); + this->aes = (hwcaps & HWCAP_AES) != 0; + this->crc32 = (hwcaps & HWCAP_CRC32) != 0; + this->pmull = (hwcaps & HWCAP_PMULL) != 0; + this->sha1 = (hwcaps & HWCAP_SHA1) != 0; + this->sha2 = (hwcaps & HWCAP_SHA2) != 0; +#ifdef HWCAP2_AES + } +#endif +} + +const Utils::ARMCapabilities Utils::ARMCAP; +#endif + +#ifdef ZT_ARCH_X64 + +Utils::CPUIDRegisters::CPUIDRegisters() noexcept +{ + uint32_t eax, ebx, ecx, edx; + +#ifdef __WINDOWS__ + int regs[4]; + __cpuid(regs,1); + eax = (uint32_t)regs[0]; + ebx = (uint32_t)regs[1]; + ecx = (uint32_t)regs[2]; + edx = (uint32_t)regs[3]; +#else + __asm__ __volatile__ ( + "cpuid" + : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) + : "a"(1), "c"(0) + ); +#endif + + rdrand = ((ecx & (1U << 30U)) != 0); + aes = (((ecx & (1U << 25U)) != 0) && ((ecx & (1U << 19U)) != 0) && ((ecx & (1U << 1U)) != 0)); + avx = ((ecx & (1U << 25U)) != 0); + +#ifdef __WINDOWS__ + __cpuid(regs,7); + eax = (uint32_t)regs[0]; + ebx = (uint32_t)regs[1]; + ecx = (uint32_t)regs[2]; + edx = (uint32_t)regs[3]; +#else + __asm__ __volatile__ ( + "cpuid" + : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) + : "a"(7), "c"(0) + ); +#endif + + vaes = aes && avx && ((ecx & (1U << 9U)) != 0); + vpclmulqdq = aes && avx && ((ecx & (1U << 10U)) != 0); + avx2 = avx && ((ebx & (1U << 5U)) != 0); + avx512f = avx && ((ebx & (1U << 16U)) != 0); + sha = ((ebx & (1U << 29U)) != 0); + fsrm = ((edx & (1U << 4U)) != 0); +} + +const Utils::CPUIDRegisters Utils::CPUID; +#endif + // Crazy hack to force memory to be securely zeroed in spite of the best efforts of optimizing compilers. static void _Utils_doBurn(volatile uint8_t *ptr,unsigned int len) { diff --git a/node/Utils.hpp b/node/Utils.hpp index 947f5b3e0..26b848dcf 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -31,6 +31,27 @@ #include "Constants.hpp" +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define ZT_CONST_TO_BE_UINT16(x) ((uint16_t)((uint16_t)((uint16_t)(x) << 8U) | (uint16_t)((uint16_t)(x) >> 8U))) +#define ZT_CONST_TO_BE_UINT64(x) ( \ + (((uint64_t)(x) & 0x00000000000000ffULL) << 56U) | \ + (((uint64_t)(x) & 0x000000000000ff00ULL) << 40U) | \ + (((uint64_t)(x) & 0x0000000000ff0000ULL) << 24U) | \ + (((uint64_t)(x) & 0x00000000ff000000ULL) << 8U) | \ + (((uint64_t)(x) & 0x000000ff00000000ULL) >> 8U) | \ + (((uint64_t)(x) & 0x0000ff0000000000ULL) >> 24U) | \ + (((uint64_t)(x) & 0x00ff000000000000ULL) >> 40U) | \ + (((uint64_t)(x) & 0xff00000000000000ULL) >> 56U)) +#else +#define ZT_CONST_TO_BE_UINT16(x) ((uint16_t)(x)) +#define ZT_CONST_TO_BE_UINT64(x) ((uint64_t)(x)) +#endif + +#define ZT_ROR64(x, r) (((x) >> (r)) | ((x) << (64 - (r)))) +#define ZT_ROL64(x, r) (((x) << (r)) | ((x) >> (64 - (r)))) +#define ZT_ROR32(x, r) (((x) >> (r)) | ((x) << (32 - (r)))) +#define ZT_ROL32(x, r) (((x) << (r)) | ((x) >> (32 - (r)))) + namespace ZeroTier { /** @@ -39,6 +60,40 @@ namespace ZeroTier { class Utils { public: + static const uint64_t ZERO256[4]; + +#ifdef ZT_ARCH_ARM_HAS_NEON + struct ARMCapabilities + { + ARMCapabilities() noexcept; + + bool aes; + bool crc32; + bool pmull; + bool sha1; + bool sha2; + }; + static const ARMCapabilities ARMCAP; +#endif + +#ifdef ZT_ARCH_X64 + struct CPUIDRegisters + { + CPUIDRegisters() noexcept; + + bool rdrand; + bool aes; + bool avx; + bool vaes; // implies AVX + bool vpclmulqdq; // implies AVX + bool avx2; + bool avx512f; + bool sha; + bool fsrm; + }; + static const CPUIDRegisters CPUID; +#endif + /** * Perform a time-invariant binary comparison * @@ -363,72 +418,341 @@ public: return true; } - // Byte swappers for big/little endian conversion - static inline uint8_t hton(uint8_t n) { return n; } - static inline int8_t hton(int8_t n) { return n; } - static inline uint16_t hton(uint16_t n) { return htons(n); } - static inline int16_t hton(int16_t n) { return (int16_t)htons((uint16_t)n); } - static inline uint32_t hton(uint32_t n) { return htonl(n); } - static inline int32_t hton(int32_t n) { return (int32_t)htonl((uint32_t)n); } - static inline uint64_t hton(uint64_t n) + /** + * Unconditionally swap bytes regardless of host byte order + * + * @param n Integer to swap + * @return Integer with bytes reversed + */ + static ZT_INLINE uint16_t swapBytes(const uint16_t n) noexcept { -#if __BYTE_ORDER == __LITTLE_ENDIAN -#if defined(__GNUC__) -#if defined(__FreeBSD__) - return bswap64(n); -#elif (!defined(__OpenBSD__)) - return __builtin_bswap64(n); -#endif -#else - return ( - ((n & 0x00000000000000FFULL) << 56) | - ((n & 0x000000000000FF00ULL) << 40) | - ((n & 0x0000000000FF0000ULL) << 24) | - ((n & 0x00000000FF000000ULL) << 8) | - ((n & 0x000000FF00000000ULL) >> 8) | - ((n & 0x0000FF0000000000ULL) >> 24) | - ((n & 0x00FF000000000000ULL) >> 40) | - ((n & 0xFF00000000000000ULL) >> 56) - ); -#endif -#else - return n; -#endif + #if defined(__GNUC__) + return __builtin_bswap16(n); + #else + #ifdef _MSC_VER + return (uint16_t)_byteswap_ushort((unsigned short)n); + #else + return htons(n); + #endif + #endif } - static inline int64_t hton(int64_t n) { return (int64_t)hton((uint64_t)n); } - static inline uint8_t ntoh(uint8_t n) { return n; } - static inline int8_t ntoh(int8_t n) { return n; } - static inline uint16_t ntoh(uint16_t n) { return ntohs(n); } - static inline int16_t ntoh(int16_t n) { return (int16_t)ntohs((uint16_t)n); } - static inline uint32_t ntoh(uint32_t n) { return ntohl(n); } - static inline int32_t ntoh(int32_t n) { return (int32_t)ntohl((uint32_t)n); } - static inline uint64_t ntoh(uint64_t n) + // These are helper adapters to load and swap integer types special cased by size + // to work with all typedef'd variants, signed/unsigned, etc. + template< typename I, unsigned int S > + class _swap_bytes_bysize; + + template< typename I > + class _swap_bytes_bysize< I, 1 > { -#if __BYTE_ORDER == __LITTLE_ENDIAN -#if defined(__GNUC__) -#if defined(__FreeBSD__) - return bswap64(n); -#elif (!defined(__OpenBSD__)) - return __builtin_bswap64(n); -#endif -#else - return ( - ((n & 0x00000000000000FFULL) << 56) | - ((n & 0x000000000000FF00ULL) << 40) | - ((n & 0x0000000000FF0000ULL) << 24) | - ((n & 0x00000000FF000000ULL) << 8) | - ((n & 0x000000FF00000000ULL) >> 8) | - ((n & 0x0000FF0000000000ULL) >> 24) | - ((n & 0x00FF000000000000ULL) >> 40) | - ((n & 0xFF00000000000000ULL) >> 56) - ); -#endif -#else + public: + static ZT_INLINE I s(const I n) noexcept + { return n; } + }; + + template< typename I > + class _swap_bytes_bysize< I, 2 > + { + public: + static ZT_INLINE I s(const I n) noexcept + { return (I)swapBytes((uint16_t)n); } + }; + + template< typename I > + class _swap_bytes_bysize< I, 4 > + { + public: + static ZT_INLINE I s(const I n) noexcept + { return (I)swapBytes((uint32_t)n); } + }; + + template< typename I > + class _swap_bytes_bysize< I, 8 > + { + public: + static ZT_INLINE I s(const I n) noexcept + { return (I)swapBytes((uint64_t)n); } + }; + + template< typename I, unsigned int S > + class _load_be_bysize; + + template< typename I > + class _load_be_bysize< I, 1 > + { + public: + static ZT_INLINE I l(const uint8_t *const p) noexcept + { return p[0]; } + }; + + template< typename I > + class _load_be_bysize< I, 2 > + { + public: + static ZT_INLINE I l(const uint8_t *const p) noexcept + { return (I)(((unsigned int)p[0] << 8U) | (unsigned int)p[1]); } + }; + + template< typename I > + class _load_be_bysize< I, 4 > + { + public: + static ZT_INLINE I l(const uint8_t *const p) noexcept + { return (I)(((uint32_t)p[0] << 24U) | ((uint32_t)p[1] << 16U) | ((uint32_t)p[2] << 8U) | (uint32_t)p[3]); } + }; + + template< typename I > + class _load_be_bysize< I, 8 > + { + public: + static ZT_INLINE I l(const uint8_t *const p) noexcept + { return (I)(((uint64_t)p[0] << 56U) | ((uint64_t)p[1] << 48U) | ((uint64_t)p[2] << 40U) | ((uint64_t)p[3] << 32U) | ((uint64_t)p[4] << 24U) | ((uint64_t)p[5] << 16U) | ((uint64_t)p[6] << 8U) | (uint64_t)p[7]); } + }; + + template< typename I, unsigned int S > + class _load_le_bysize; + + template< typename I > + class _load_le_bysize< I, 1 > + { + public: + static ZT_INLINE I l(const uint8_t *const p) noexcept + { return p[0]; } + }; + + template< typename I > + class _load_le_bysize< I, 2 > + { + public: + static ZT_INLINE I l(const uint8_t *const p) noexcept + { return (I)((unsigned int)p[0] | ((unsigned int)p[1] << 8U)); } + }; + + template< typename I > + class _load_le_bysize< I, 4 > + { + public: + static ZT_INLINE I l(const uint8_t *const p) noexcept + { return (I)((uint32_t)p[0] | ((uint32_t)p[1] << 8U) | ((uint32_t)p[2] << 16U) | ((uint32_t)p[3] << 24U)); } + }; + + template< typename I > + class _load_le_bysize< I, 8 > + { + public: + static ZT_INLINE I l(const uint8_t *const p) noexcept + { return (I)((uint64_t)p[0] | ((uint64_t)p[1] << 8U) | ((uint64_t)p[2] << 16U) | ((uint64_t)p[3] << 24U) | ((uint64_t)p[4] << 32U) | ((uint64_t)p[5] << 40U) | ((uint64_t)p[6] << 48U) | ((uint64_t)p[7]) << 56U); } + }; + + /** + * Convert any signed or unsigned integer type to big-endian ("network") byte order + * + * @tparam I Integer type (usually inferred) + * @param n Value to convert + * @return Value in big-endian order + */ + template< typename I > + static ZT_INLINE I hton(const I n) noexcept + { + #if __BYTE_ORDER == __LITTLE_ENDIAN + return _swap_bytes_bysize< I, sizeof(I) >::s(n); + #else return n; -#endif + #endif + } + + /** + * Convert any signed or unsigned integer type to host byte order from big-endian ("network") byte order + * + * @tparam I Integer type (usually inferred) + * @param n Value to convert + * @return Value in host byte order + */ + template< typename I > + static ZT_INLINE I ntoh(const I n) noexcept + { + #if __BYTE_ORDER == __LITTLE_ENDIAN + return _swap_bytes_bysize< I, sizeof(I) >::s(n); + #else + return n; + #endif + } + + /** + * Copy bits from memory into an integer type without modifying their order + * + * @tparam I Type to load + * @param p Byte stream, must be at least sizeof(I) in size + * @return Loaded raw integer + */ + template< typename I > + static ZT_INLINE I loadMachineEndian(const void *const p) noexcept + { + #ifdef ZT_NO_UNALIGNED_ACCESS + I tmp; + for(int i=0;i<(int)sizeof(I);++i) + reinterpret_cast(&tmp)[i] = reinterpret_cast(p)[i]; + return tmp; + #else + return *reinterpret_cast(p); + #endif + } + + /** + * Copy bits from memory into an integer type without modifying their order + * + * @tparam I Type to store + * @param p Byte array (must be at least sizeof(I)) + * @param i Integer to store + */ + template< typename I > + static ZT_INLINE void storeMachineEndian(void *const p, const I i) noexcept + { + #ifdef ZT_NO_UNALIGNED_ACCESS + for(unsigned int k=0;k(p)[k] = reinterpret_cast(&i)[k]; + #else + *reinterpret_cast(p) = i; + #endif + } + + /** + * Decode a big-endian value from a byte stream + * + * @tparam I Type to decode (should be unsigned e.g. uint32_t or uint64_t) + * @param p Byte stream, must be at least sizeof(I) in size + * @return Decoded integer + */ + template< typename I > + static ZT_INLINE I loadBigEndian(const void *const p) noexcept + { + #ifdef ZT_NO_UNALIGNED_ACCESS + return _load_be_bysize::l(reinterpret_cast(p)); + #else + return ntoh(*reinterpret_cast(p)); + #endif + } + + /** + * Save an integer in big-endian format + * + * @tparam I Integer type to store (usually inferred) + * @param p Byte stream to write (must be at least sizeof(I)) + * #param i Integer to write + */ + template< typename I > + static ZT_INLINE void storeBigEndian(void *const p, I i) noexcept + { + #ifdef ZT_NO_UNALIGNED_ACCESS + storeMachineEndian(p,hton(i)); + #else + *reinterpret_cast(p) = hton(i); + #endif + } + + /** + * Decode a little-endian value from a byte stream + * + * @tparam I Type to decode + * @param p Byte stream, must be at least sizeof(I) in size + * @return Decoded integer + */ + template< typename I > + static ZT_INLINE I loadLittleEndian(const void *const p) noexcept + { + #if __BYTE_ORDER == __BIG_ENDIAN || defined(ZT_NO_UNALIGNED_ACCESS) + return _load_le_bysize::l(reinterpret_cast(p)); + #else + return *reinterpret_cast(p); + #endif + } + + /** + * Save an integer in little-endian format + * + * @tparam I Integer type to store (usually inferred) + * @param p Byte stream to write (must be at least sizeof(I)) + * #param i Integer to write + */ + template< typename I > + static ZT_INLINE void storeLittleEndian(void *const p, const I i) noexcept + { + #if __BYTE_ORDER == __BIG_ENDIAN + storeMachineEndian(p,_swap_bytes_bysize::s(i)); + #else + #ifdef ZT_NO_UNALIGNED_ACCESS + storeMachineEndian(p,i); + #else + *reinterpret_cast(p) = i; + #endif + #endif + } + + /** + * Copy memory block whose size is known at compile time. + * + * @tparam L Size of memory + * @param dest Destination memory + * @param src Source memory + */ + template< unsigned long L > + static ZT_INLINE void copy(void *dest, const void *src) noexcept + { + #if defined(ZT_ARCH_X64) && defined(__GNUC__) + uintptr_t l = L; + __asm__ __volatile__ ("cld ; rep movsb" : "+c"(l), "+S"(src), "+D"(dest) :: "memory"); + #else + memcpy(dest, src, L); + #endif + } + + /** + * Copy memory block whose size is known at run time + * + * @param dest Destination memory + * @param src Source memory + * @param len Bytes to copy + */ + static ZT_INLINE void copy(void *dest, const void *src, unsigned long len) noexcept + { + #if defined(ZT_ARCH_X64) && defined(__GNUC__) + __asm__ __volatile__ ("cld ; rep movsb" : "+c"(len), "+S"(src), "+D"(dest) :: "memory"); + #else + memcpy(dest, src, len); + #endif + } + + /** + * Zero memory block whose size is known at compile time + * + * @tparam L Size in bytes + * @param dest Memory to zero + */ + template< unsigned long L > + static ZT_INLINE void zero(void *dest) noexcept + { + #if defined(ZT_ARCH_X64) && defined(__GNUC__) + uintptr_t l = L; + __asm__ __volatile__ ("cld ; rep stosb" :"+c" (l), "+D" (dest) : "a" (0) : "memory"); + #else + memset(dest, 0, L); + #endif + } + + /** + * Zero memory block whose size is known at run time + * + * @param dest Memory to zero + * @param len Size in bytes + */ + static ZT_INLINE void zero(void *dest, unsigned long len) noexcept + { + #if defined(ZT_ARCH_X64) && defined(__GNUC__) + __asm__ __volatile__ ("cld ; rep stosb" :"+c" (len), "+D" (dest) : "a" (0) : "memory"); + #else + memset(dest, 0, len); + #endif } - static inline int64_t ntoh(int64_t n) { return (int64_t)ntoh((uint64_t)n); } /** * Hexadecimal characters 0-f diff --git a/objects.mk b/objects.mk index b55ba3044..abed41af7 100644 --- a/objects.mk +++ b/objects.mk @@ -1,4 +1,5 @@ CORE_OBJS=\ + node/AES.o \ node/C25519.o \ node/Capability.o \ node/CertificateOfMembership.o \ From 2ac49d99dd2159827330a890e99d2e828b39e106 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 21 Aug 2020 14:23:31 -0700 Subject: [PATCH 130/362] AES integrated on send side. --- node/Bond.cpp | 6 +- node/C25519.cpp | 16 +- node/Constants.hpp | 2 + node/Identity.cpp | 2 +- node/Identity.hpp | 2 +- node/IncomingPacket.cpp | 20 +- node/Multicaster.cpp | 2 +- node/Packet.cpp | 78 +++-- node/Packet.hpp | 27 +- node/Peer.cpp | 21 +- node/Peer.hpp | 5 + node/SHA512.cpp | 591 +++++++++++++++--------------------- node/SHA512.hpp | 74 ++++- node/Switch.cpp | 11 +- service/SoftwareUpdater.cpp | 16 +- 15 files changed, 446 insertions(+), 427 deletions(-) diff --git a/node/Bond.cpp b/node/Bond.cpp index 70d0e5c5a..3f65c0fe4 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -618,7 +618,7 @@ void Bond::sendPATH_NEGOTIATION_REQUEST(void *tPtr, const SharedPtr &path) Packet outp(_peer->_id.address(),RR->identity.address(),Packet::VERB_PATH_NEGOTIATION_REQUEST); outp.append(_localUtility); if (path->address()) { - outp.armor(_peer->key(),false); + outp.armor(_peer->key(),false,_peer->aesKeysIfSupported()); RR->node->putPacket(tPtr,path->localSocket(),path->address(),outp.data(),outp.size()); } } @@ -639,7 +639,7 @@ void Bond::sendACK(void *tPtr, const SharedPtr &path,const int64_t localSo //RR->t->bondStateMessage(NULL, traceMsg); outp.append(bytesToAck); if (atAddress) { - outp.armor(_peer->key(),false); + outp.armor(_peer->key(),false,_peer->aesKeysIfSupported()); RR->node->putPacket(tPtr,localSocket,atAddress,outp.data(),outp.size()); } else { RR->sw->send(tPtr,outp,false); @@ -662,7 +662,7 @@ void Bond::sendQOS_MEASUREMENT(void *tPtr,const SharedPtr &path,const int6 int16_t len = generateQoSPacket(path, _now,qosData); outp.append(qosData,len); if (atAddress) { - outp.armor(_peer->key(),false); + outp.armor(_peer->key(),false,_peer->aesKeysIfSupported()); RR->node->putPacket(tPtr,localSocket,atAddress,outp.data(),outp.size()); } else { RR->sw->send(tPtr,outp,false); diff --git a/node/C25519.cpp b/node/C25519.cpp index d7c7011fd..aec34835e 100644 --- a/node/C25519.cpp +++ b/node/C25519.cpp @@ -2439,7 +2439,7 @@ static inline void get_hram(unsigned char *hram, const unsigned char *sm, const for (i = 32;i < 64;++i) playground[i] = pk[i-32]; for (i = 64;i < smlen;++i) playground[i] = sm[i]; - ZeroTier::SHA512::hash(hram,playground,(unsigned int)smlen); + ZeroTier::SHA512(hram,playground,(unsigned int)smlen); } ////////////////////////////////////////////////////////////////////////////// @@ -2459,11 +2459,11 @@ void C25519::agree(const C25519::Private &mine,const C25519::Public &their,void unsigned char digest[64]; crypto_scalarmult(rawkey,mine.data,their.data); - SHA512::hash(digest,rawkey,32); + SHA512(digest,rawkey,32); for(unsigned int i=0,k=0;idata,ZT_C25519_PRIVATE_KEY_LEN); + SHA512(sha,_privateKey->data,ZT_C25519_PRIVATE_KEY_LEN); return true; } return false; diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 94f7f6301..9ff6edd58 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -294,7 +294,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool outp.append((uint8_t)Packet::VERB_HELLO); outp.append((uint64_t)pid); outp.append((uint8_t)Packet::ERROR_IDENTITY_COLLISION); - outp.armor(key,true); + outp.armor(key,true,peer->aesKeysIfSupported()); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } else { RR->t->incomingPacketMessageAuthenticationFailure(tPtr,_path,pid,fromAddress,hops(),"invalid MAC"); @@ -444,8 +444,8 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool } outp.setAt(worldUpdateSizeAt,(uint16_t)(outp.size() - (worldUpdateSizeAt + 2))); + outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); - outp.armor(peer->key(),true); _path->send(RR,tPtr,outp.data(),outp.size(),now); peer->setRemoteVersion(protoVersion,vMajor,vMinor,vRevision); // important for this to go first so received() knows the version @@ -593,7 +593,7 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const Shar } if (count > 0) { - outp.armor(peer->key(),true); + outp.armor(peer->key(),true,peer->aesKeysIfSupported()); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -816,8 +816,8 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const outp.append((uint64_t)packetId()); outp.append((uint64_t)nwid); const int64_t now = RR->node->now(); + outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); - outp.armor(peer->key(),true); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -842,8 +842,8 @@ bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const Share outp.append((uint64_t)pid); if (size() > ZT_PACKET_IDX_PAYLOAD) outp.append(reinterpret_cast(data()) + ZT_PACKET_IDX_PAYLOAD,size() - ZT_PACKET_IDX_PAYLOAD); + outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); - outp.armor(peer->key(),true); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); peer->received(tPtr,_path,hops(),pid,payloadLength(),Packet::VERB_ECHO,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); @@ -1016,7 +1016,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,void outp.append(requestPacketId); outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION); outp.append(nwid); - outp.armor(peer->key(),true); + outp.armor(peer->key(),true,peer->aesKeysIfSupported()); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -1037,8 +1037,8 @@ bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,c outp.append((uint64_t)network->id()); outp.append((uint64_t)configUpdateId); const int64_t now = RR->node->now(); + outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); - outp.armor(peer->key(),true); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } } @@ -1086,8 +1086,8 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr outp.append((uint32_t)mg.adi()); const unsigned int gatheredLocally = RR->mc->gather(peer->address(),nwid,mg,outp,gatherLimit); if (gatheredLocally > 0) { + outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); - outp.armor(peer->key(),true); _path->send(RR,tPtr,outp.data(),outp.size(),now); } } @@ -1185,8 +1185,8 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, outp.append((unsigned char)0x02); // flag 0x02 = contains gather results if (RR->mc->gather(peer->address(),nwid,to,outp,gatherLimit)) { const int64_t now = RR->node->now(); + outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); - outp.armor(peer->key(),true); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } } @@ -1327,7 +1327,7 @@ void IncomingPacket::_sendErrorNeedCredentials(const RuntimeEnvironment *RR,void outp.append(packetId()); outp.append((uint8_t)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE); outp.append(nwid); - outp.armor(peer->key(),true); + outp.armor(peer->key(),true,peer->aesKeysIfSupported()); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp index 6b9bdd374..3b48b799e 100644 --- a/node/Multicaster.cpp +++ b/node/Multicaster.cpp @@ -194,7 +194,7 @@ void Multicaster::send( outp.append((uint16_t)etherType); outp.append(data,len); if (!network->config().disableCompression()) outp.compress(); - outp.armor(bestMulticastReplicator->key(),true); + outp.armor(bestMulticastReplicator->key(),true,bestMulticastReplicator->aesKeysIfSupported()); bestMulticastReplicatorPath->send(RR,tPtr,outp.data(),outp.size(),now); return; } diff --git a/node/Packet.cpp b/node/Packet.cpp index 395c0e192..04d8f92e5 100644 --- a/node/Packet.cpp +++ b/node/Packet.cpp @@ -876,39 +876,61 @@ static inline int LZ4_decompress_safe(const char* source, char* dest, int compre const unsigned char Packet::ZERO_KEY[32] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; -void Packet::armor(const void *key,bool encryptPayload) +void Packet::armor(const void *key,bool encryptPayload,const AES aesKeys[2]) { - uint8_t mangledKey[32]; uint8_t *const data = reinterpret_cast(unsafeData()); + if ((aesKeys) && (encryptPayload)) { + setCipher(ZT_PROTO_CIPHER_SUITE__AES_GMAC_SIV); - // Set flag now, since it affects key mangle function - setCipher(encryptPayload ? ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012 : ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE); - - _salsa20MangleKey((const unsigned char *)key,mangledKey); - - if (ZT_HAS_FAST_CRYPTO()) { - const unsigned int encryptLen = (encryptPayload) ? (size() - ZT_PACKET_IDX_VERB) : 0; - uint64_t keyStream[(ZT_PROTO_MAX_PACKET_LENGTH + 64 + 8) / 8]; - ZT_FAST_SINGLE_PASS_SALSA2012(keyStream,encryptLen + 64,(data + ZT_PACKET_IDX_IV),mangledKey); - Salsa20::memxor(data + ZT_PACKET_IDX_VERB,reinterpret_cast(keyStream + 8),encryptLen); - uint64_t mac[2]; - Poly1305::compute(mac,data + ZT_PACKET_IDX_VERB,size() - ZT_PACKET_IDX_VERB,keyStream); -#ifdef ZT_NO_TYPE_PUNNING - memcpy(data + ZT_PACKET_IDX_MAC,mac,8); -#else - (*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) = mac[0]; -#endif - } else { - Salsa20 s20(mangledKey,data + ZT_PACKET_IDX_IV); - uint64_t macKey[4]; - s20.crypt12(ZERO_KEY,macKey,sizeof(macKey)); uint8_t *const payload = data + ZT_PACKET_IDX_VERB; const unsigned int payloadLen = size() - ZT_PACKET_IDX_VERB; - if (encryptPayload) - s20.crypt12(payload,payload,payloadLen); - uint64_t mac[2]; - Poly1305::compute(mac,payload,payloadLen,macKey); - memcpy(data + ZT_PACKET_IDX_MAC,mac,8); + + AES::GMACSIVEncryptor enc(aesKeys[0],aesKeys[1]); + enc.init(Utils::loadMachineEndian(data + ZT_PACKET_IDX_IV),payload); + enc.aad(data + ZT_PACKET_IDX_DEST,11); + enc.update1(payload,payloadLen); + enc.finish1(); + enc.update2(payload,payloadLen); + const uint64_t *const tag = enc.finish2(); + +#ifdef ZT_NO_UNALIGNED_ACCESS + Utils::copy<8>(data,tag); + Utils::copy<8>(data + ZT_PACKET_IDX_MAC,tag + 1); +#else + *reinterpret_cast(data) = tag[0]; + *reinterpret_cast(data + ZT_PACKET_IDX_MAC) = tag[1]; +#endif + } else { + uint8_t mangledKey[32]; + setCipher(encryptPayload ? ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012 : ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE); + _salsa20MangleKey((const unsigned char *)key,mangledKey); + if (ZT_HAS_FAST_CRYPTO()) { + const unsigned int encryptLen = (encryptPayload) ? (size() - ZT_PACKET_IDX_VERB) : 0; + uint64_t keyStream[(ZT_PROTO_MAX_PACKET_LENGTH + 64 + 8) / 8]; + ZT_FAST_SINGLE_PASS_SALSA2012(keyStream,encryptLen + 64,(data + ZT_PACKET_IDX_IV),mangledKey); + Salsa20::memxor(data + ZT_PACKET_IDX_VERB,reinterpret_cast(keyStream + 8),encryptLen); + uint64_t mac[2]; + Poly1305::compute(mac,data + ZT_PACKET_IDX_VERB,size() - ZT_PACKET_IDX_VERB,keyStream); +#ifdef ZT_NO_TYPE_PUNNING + memcpy(data + ZT_PACKET_IDX_MAC,mac,8); +#else + (*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) = mac[0]; +#endif + } else { + Salsa20 s20(mangledKey,data + ZT_PACKET_IDX_IV); + + uint64_t macKey[4]; + s20.crypt12(ZERO_KEY,macKey,sizeof(macKey)); + + uint8_t *const payload = data + ZT_PACKET_IDX_VERB; + const unsigned int payloadLen = size() - ZT_PACKET_IDX_VERB; + if (encryptPayload) + s20.crypt12(payload,payload,payloadLen); + uint64_t mac[2]; + + Poly1305::compute(mac,payload,payloadLen,macKey); + memcpy(data + ZT_PACKET_IDX_MAC,mac,8); + } } } diff --git a/node/Packet.hpp b/node/Packet.hpp index 1ffca5a5c..9089e5264 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -26,6 +26,7 @@ #include "Address.hpp" #include "Poly1305.hpp" #include "Salsa20.hpp" +#include "AES.hpp" #include "Utils.hpp" #include "Buffer.hpp" @@ -55,10 +56,12 @@ * + Tags and Capabilities * + Inline push of CertificateOfMembership deprecated * 9 - 1.2.0 ... 1.2.14 - * 10 - 1.4.0 ... CURRENT - * + Multipath capability and load balancing (tentative) + * 10 - 1.4.0 ... 1.4.6 + * 11 - 1.4.8 ... end of 1.4 series + * + Multipath capability and load balancing (beta) + * + AES-GMAC-SIV backported for faster peer-to-peer crypto */ -#define ZT_PROTO_VERSION 10 +#define ZT_PROTO_VERSION 11 /** * Minimum supported protocol version @@ -96,6 +99,21 @@ */ #define ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012 1 +/** + * AES-GMAC-SIV backported from 2.x + */ +#define ZT_PROTO_CIPHER_SUITE__AES_GMAC_SIV 3 + +/** + * AES-GMAC-SIV first of two keys + */ +#define ZT_KBKDF_LABEL_AES_GMAC_SIV_K0 '0' + +/** + * AES-GMAC-SIV second of two keys + */ +#define ZT_KBKDF_LABEL_AES_GMAC_SIV_K1 '1' + /** * Cipher suite: NONE * @@ -1295,8 +1313,9 @@ public: * * @param key 32-byte key * @param encryptPayload If true, encrypt packet payload, else just MAC + * @param aes Use new AES-GMAC-SIV constrution */ - void armor(const void *key,bool encryptPayload); + void armor(const void *key,bool encryptPayload,const AES aesKeys[2]); /** * Verify and (if encrypted) decrypt packet diff --git a/node/Peer.cpp b/node/Peer.cpp index bf39c7d14..b2b04c17f 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -61,6 +61,13 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident if (!myIdentity.agree(peerIdentity,_key,ZT_PEER_SECRET_KEY_LENGTH)) { throw ZT_EXCEPTION_INVALID_ARGUMENT; } + + uint8_t ktmp[32]; + KBKDFHMACSHA384(_key,ZT_KBKDF_LABEL_AES_GMAC_SIV_K0,0,0,ktmp); + _aesKeys[0].init(ktmp); + KBKDFHMACSHA384(_key,ZT_KBKDF_LABEL_AES_GMAC_SIV_K1,0,0,ktmp); + _aesKeys[0].init(ktmp); + Utils::burn(ktmp, 32); } void Peer::received( @@ -209,7 +216,7 @@ void Peer::received( if (count) { outp->setAt(ZT_PACKET_IDX_PAYLOAD,(uint16_t)count); outp->compress(); - outp->armor(_key,true); + outp->armor(_key,true,aesKeysIfSupported()); path->send(RR,tPtr,outp->data(),outp->size(),now); } delete outp; @@ -347,7 +354,7 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o outp.append((uint8_t)4); outp.append(other->_paths[theirs].p->address().rawIpData(),4); } - outp.armor(_key,true); + outp.armor(_key,true,aesKeysIfSupported()); _paths[mine].p->send(RR,tPtr,outp.data(),outp.size(),now); } else { Packet outp(other->_id.address(),RR->identity.address(),Packet::VERB_RENDEZVOUS); @@ -361,7 +368,7 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o outp.append((uint8_t)4); outp.append(_paths[mine].p->address().rawIpData(),4); } - outp.armor(other->_key,true); + outp.armor(other->_key,true,aesKeysIfSupported()); other->_paths[theirs].p->send(RR,tPtr,outp.data(),outp.size(),now); } ++alt; @@ -402,12 +409,12 @@ void Peer::sendHELLO(void *tPtr,const int64_t localSocket,const InetAddress &atA outp.cryptField(_key,startCryptedPortionAt,outp.size() - startCryptedPortionAt); - RR->node->expectReplyTo(outp.packetId()); - if (atAddress) { - outp.armor(_key,false); // false == don't encrypt full payload, but add MAC + outp.armor(_key,false,aesKeysIfSupported()); // false == don't encrypt full payload, but add MAC + RR->node->expectReplyTo(outp.packetId()); RR->node->putPacket(tPtr,localSocket,atAddress,outp.data(),outp.size()); } else { + RR->node->expectReplyTo(outp.packetId()); RR->sw->send(tPtr,outp,false); // false == don't encrypt full payload, but add MAC } } @@ -416,8 +423,8 @@ void Peer::attemptToContactAt(void *tPtr,const int64_t localSocket,const InetAdd { if ( (!sendFullHello) && (_vProto >= 5) && (!((_vMajor == 1)&&(_vMinor == 1)&&(_vRevision == 0))) ) { Packet outp(_id.address(),RR->identity.address(),Packet::VERB_ECHO); + outp.armor(_key,true,aesKeysIfSupported()); RR->node->expectReplyTo(outp.packetId()); - outp.armor(_key,true); RR->node->putPacket(tPtr,localSocket,atAddress,outp.data(),outp.size()); } else { sendHELLO(tPtr,localSocket,atAddress,now); diff --git a/node/Peer.hpp b/node/Peer.hpp index 80a2264e3..4c8865c65 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -33,6 +33,7 @@ #include "Mutex.hpp" #include "Bond.hpp" #include "BondController.hpp" +#include "AES.hpp" #define ZT_PEER_MAX_SERIALIZED_STATE_SIZE (sizeof(Peer) + 32 + (sizeof(Path) * 2)) @@ -532,6 +533,9 @@ public: */ inline int8_t bondingPolicy() { return _bondingPolicy; } + const AES *aesKeysIfSupported() const + { return (_vProto >= 10) ? _aesKeys : (const AES *)0; } + private: struct _PeerPath { @@ -542,6 +546,7 @@ private: }; uint8_t _key[ZT_PEER_SECRET_KEY_LENGTH]; + AES _aesKeys[2]; const RuntimeEnvironment *RR; diff --git a/node/SHA512.cpp b/node/SHA512.cpp index d3c938afb..ee28ed8bc 100644 --- a/node/SHA512.cpp +++ b/node/SHA512.cpp @@ -1,367 +1,274 @@ -// Code taken from NaCl by D. J. Bernstein and others -// Public domain - -/* -20080913 -D. J. Bernstein -Public domain. -*/ - -#include -#include -#include +// This code is public domain, taken from a PD crypto source file on GitHub. #include "SHA512.hpp" #include "Utils.hpp" -#ifdef __APPLE__ -#include -#define ZT_HAVE_NATIVE_SHA512 namespace ZeroTier { -void SHA512::hash(void *digest,const void *data,unsigned int len) -{ - CC_SHA512_CTX ctx; - CC_SHA512_Init(&ctx); - CC_SHA512_Update(&ctx,data,len); - CC_SHA512_Final(reinterpret_cast(digest),&ctx); -} -} -#endif - -#ifdef ZT_USE_LIBCRYPTO -#include -#define ZT_HAVE_NATIVE_SHA512 -namespace ZeroTier { -void SHA512::hash(void *digest,const void *data,unsigned int len) -{ - SHA512_CTX ctx; - SHA512_Init(&ctx); - SHA512_Update(&ctx,data,len); - SHA512_Final(reinterpret_cast(digest),&ctx); -} -} -#endif #ifndef ZT_HAVE_NATIVE_SHA512 -namespace ZeroTier { +namespace { -#define uint64 uint64_t - -#ifdef ZT_NO_TYPE_PUNNING - -static uint64 load_bigendian(const unsigned char *x) -{ - return - (uint64) (x[7]) \ - | (((uint64) (x[6])) << 8) \ - | (((uint64) (x[5])) << 16) \ - | (((uint64) (x[4])) << 24) \ - | (((uint64) (x[3])) << 32) \ - | (((uint64) (x[2])) << 40) \ - | (((uint64) (x[1])) << 48) \ - | (((uint64) (x[0])) << 56) - ; -} - -static void store_bigendian(unsigned char *x,uint64 u) -{ - x[7] = u; u >>= 8; - x[6] = u; u >>= 8; - x[5] = u; u >>= 8; - x[4] = u; u >>= 8; - x[3] = u; u >>= 8; - x[2] = u; u >>= 8; - x[1] = u; u >>= 8; - x[0] = u; -} - -#else // !ZT_NO_TYPE_PUNNING - -#define load_bigendian(x) Utils::ntoh(*((const uint64_t *)(x))) -#define store_bigendian(x,u) (*((uint64_t *)(x)) = Utils::hton((u))) - -#endif // ZT_NO_TYPE_PUNNING - -#define SHR(x,c) ((x) >> (c)) -#define ROTR(x,c) (((x) >> (c)) | ((x) << (64 - (c)))) - -#define Ch(x,y,z) ((x & y) ^ (~x & z)) -#define Maj(x,y,z) ((x & y) ^ (x & z) ^ (y & z)) -#define Sigma0(x) (ROTR(x,28) ^ ROTR(x,34) ^ ROTR(x,39)) -#define Sigma1(x) (ROTR(x,14) ^ ROTR(x,18) ^ ROTR(x,41)) -#define sigma0(x) (ROTR(x, 1) ^ ROTR(x, 8) ^ SHR(x,7)) -#define sigma1(x) (ROTR(x,19) ^ ROTR(x,61) ^ SHR(x,6)) - -#define M(w0,w14,w9,w1) w0 = sigma1(w14) + w9 + sigma0(w1) + w0; - -#define EXPAND \ - M(w0 ,w14,w9 ,w1 ) \ - M(w1 ,w15,w10,w2 ) \ - M(w2 ,w0 ,w11,w3 ) \ - M(w3 ,w1 ,w12,w4 ) \ - M(w4 ,w2 ,w13,w5 ) \ - M(w5 ,w3 ,w14,w6 ) \ - M(w6 ,w4 ,w15,w7 ) \ - M(w7 ,w5 ,w0 ,w8 ) \ - M(w8 ,w6 ,w1 ,w9 ) \ - M(w9 ,w7 ,w2 ,w10) \ - M(w10,w8 ,w3 ,w11) \ - M(w11,w9 ,w4 ,w12) \ - M(w12,w10,w5 ,w13) \ - M(w13,w11,w6 ,w14) \ - M(w14,w12,w7 ,w15) \ - M(w15,w13,w8 ,w0 ) - -#define F(w,k) \ - T1 = h + Sigma1(e) + Ch(e,f,g) + k + w; \ - T2 = Sigma0(a) + Maj(a,b,c); \ - h = g; \ - g = f; \ - f = e; \ - e = d + T1; \ - d = c; \ - c = b; \ - b = a; \ - a = T1 + T2; - -static inline int crypto_hashblocks(unsigned char *statebytes,const unsigned char *in,unsigned long long inlen) -{ - uint64 state[8]; - uint64 a; - uint64 b; - uint64 c; - uint64 d; - uint64 e; - uint64 f; - uint64 g; - uint64 h; - uint64 T1; - uint64 T2; - - a = load_bigendian(statebytes + 0); state[0] = a; - b = load_bigendian(statebytes + 8); state[1] = b; - c = load_bigendian(statebytes + 16); state[2] = c; - d = load_bigendian(statebytes + 24); state[3] = d; - e = load_bigendian(statebytes + 32); state[4] = e; - f = load_bigendian(statebytes + 40); state[5] = f; - g = load_bigendian(statebytes + 48); state[6] = g; - h = load_bigendian(statebytes + 56); state[7] = h; - - while (inlen >= 128) { - uint64 w0 = load_bigendian(in + 0); - uint64 w1 = load_bigendian(in + 8); - uint64 w2 = load_bigendian(in + 16); - uint64 w3 = load_bigendian(in + 24); - uint64 w4 = load_bigendian(in + 32); - uint64 w5 = load_bigendian(in + 40); - uint64 w6 = load_bigendian(in + 48); - uint64 w7 = load_bigendian(in + 56); - uint64 w8 = load_bigendian(in + 64); - uint64 w9 = load_bigendian(in + 72); - uint64 w10 = load_bigendian(in + 80); - uint64 w11 = load_bigendian(in + 88); - uint64 w12 = load_bigendian(in + 96); - uint64 w13 = load_bigendian(in + 104); - uint64 w14 = load_bigendian(in + 112); - uint64 w15 = load_bigendian(in + 120); - - F(w0 ,0x428a2f98d728ae22ULL) - F(w1 ,0x7137449123ef65cdULL) - F(w2 ,0xb5c0fbcfec4d3b2fULL) - F(w3 ,0xe9b5dba58189dbbcULL) - F(w4 ,0x3956c25bf348b538ULL) - F(w5 ,0x59f111f1b605d019ULL) - F(w6 ,0x923f82a4af194f9bULL) - F(w7 ,0xab1c5ed5da6d8118ULL) - F(w8 ,0xd807aa98a3030242ULL) - F(w9 ,0x12835b0145706fbeULL) - F(w10,0x243185be4ee4b28cULL) - F(w11,0x550c7dc3d5ffb4e2ULL) - F(w12,0x72be5d74f27b896fULL) - F(w13,0x80deb1fe3b1696b1ULL) - F(w14,0x9bdc06a725c71235ULL) - F(w15,0xc19bf174cf692694ULL) - - EXPAND - - F(w0 ,0xe49b69c19ef14ad2ULL) - F(w1 ,0xefbe4786384f25e3ULL) - F(w2 ,0x0fc19dc68b8cd5b5ULL) - F(w3 ,0x240ca1cc77ac9c65ULL) - F(w4 ,0x2de92c6f592b0275ULL) - F(w5 ,0x4a7484aa6ea6e483ULL) - F(w6 ,0x5cb0a9dcbd41fbd4ULL) - F(w7 ,0x76f988da831153b5ULL) - F(w8 ,0x983e5152ee66dfabULL) - F(w9 ,0xa831c66d2db43210ULL) - F(w10,0xb00327c898fb213fULL) - F(w11,0xbf597fc7beef0ee4ULL) - F(w12,0xc6e00bf33da88fc2ULL) - F(w13,0xd5a79147930aa725ULL) - F(w14,0x06ca6351e003826fULL) - F(w15,0x142929670a0e6e70ULL) - - EXPAND - - F(w0 ,0x27b70a8546d22ffcULL) - F(w1 ,0x2e1b21385c26c926ULL) - F(w2 ,0x4d2c6dfc5ac42aedULL) - F(w3 ,0x53380d139d95b3dfULL) - F(w4 ,0x650a73548baf63deULL) - F(w5 ,0x766a0abb3c77b2a8ULL) - F(w6 ,0x81c2c92e47edaee6ULL) - F(w7 ,0x92722c851482353bULL) - F(w8 ,0xa2bfe8a14cf10364ULL) - F(w9 ,0xa81a664bbc423001ULL) - F(w10,0xc24b8b70d0f89791ULL) - F(w11,0xc76c51a30654be30ULL) - F(w12,0xd192e819d6ef5218ULL) - F(w13,0xd69906245565a910ULL) - F(w14,0xf40e35855771202aULL) - F(w15,0x106aa07032bbd1b8ULL) - - EXPAND - - F(w0 ,0x19a4c116b8d2d0c8ULL) - F(w1 ,0x1e376c085141ab53ULL) - F(w2 ,0x2748774cdf8eeb99ULL) - F(w3 ,0x34b0bcb5e19b48a8ULL) - F(w4 ,0x391c0cb3c5c95a63ULL) - F(w5 ,0x4ed8aa4ae3418acbULL) - F(w6 ,0x5b9cca4f7763e373ULL) - F(w7 ,0x682e6ff3d6b2b8a3ULL) - F(w8 ,0x748f82ee5defb2fcULL) - F(w9 ,0x78a5636f43172f60ULL) - F(w10,0x84c87814a1f0ab72ULL) - F(w11,0x8cc702081a6439ecULL) - F(w12,0x90befffa23631e28ULL) - F(w13,0xa4506cebde82bde9ULL) - F(w14,0xbef9a3f7b2c67915ULL) - F(w15,0xc67178f2e372532bULL) - - EXPAND - - F(w0 ,0xca273eceea26619cULL) - F(w1 ,0xd186b8c721c0c207ULL) - F(w2 ,0xeada7dd6cde0eb1eULL) - F(w3 ,0xf57d4f7fee6ed178ULL) - F(w4 ,0x06f067aa72176fbaULL) - F(w5 ,0x0a637dc5a2c898a6ULL) - F(w6 ,0x113f9804bef90daeULL) - F(w7 ,0x1b710b35131c471bULL) - F(w8 ,0x28db77f523047d84ULL) - F(w9 ,0x32caab7b40c72493ULL) - F(w10,0x3c9ebe0a15c9bebcULL) - F(w11,0x431d67c49c100d4cULL) - F(w12,0x4cc5d4becb3e42b6ULL) - F(w13,0x597f299cfc657e2aULL) - F(w14,0x5fcb6fab3ad6faecULL) - F(w15,0x6c44198c4a475817ULL) - - a += state[0]; - b += state[1]; - c += state[2]; - d += state[3]; - e += state[4]; - f += state[5]; - g += state[6]; - h += state[7]; - - state[0] = a; - state[1] = b; - state[2] = c; - state[3] = d; - state[4] = e; - state[5] = f; - state[6] = g; - state[7] = h; - - in += 128; - inlen -= 128; - } - - store_bigendian(statebytes + 0,state[0]); - store_bigendian(statebytes + 8,state[1]); - store_bigendian(statebytes + 16,state[2]); - store_bigendian(statebytes + 24,state[3]); - store_bigendian(statebytes + 32,state[4]); - store_bigendian(statebytes + 40,state[5]); - store_bigendian(statebytes + 48,state[6]); - store_bigendian(statebytes + 56,state[7]); - - return 0; -} - -#define blocks crypto_hashblocks - -static const unsigned char iv[64] = { - 0x6a,0x09,0xe6,0x67,0xf3,0xbc,0xc9,0x08, - 0xbb,0x67,0xae,0x85,0x84,0xca,0xa7,0x3b, - 0x3c,0x6e,0xf3,0x72,0xfe,0x94,0xf8,0x2b, - 0xa5,0x4f,0xf5,0x3a,0x5f,0x1d,0x36,0xf1, - 0x51,0x0e,0x52,0x7f,0xad,0xe6,0x82,0xd1, - 0x9b,0x05,0x68,0x8c,0x2b,0x3e,0x6c,0x1f, - 0x1f,0x83,0xd9,0xab,0xfb,0x41,0xbd,0x6b, - 0x5b,0xe0,0xcd,0x19,0x13,0x7e,0x21,0x79 +struct sha512_state { + uint64_t length,state[8]; + unsigned long curlen; + uint8_t buf[128]; }; -void SHA512::hash(void *digest,const void *data,unsigned int len) +static const uint64_t K[80] = { + 0x428a2f98d728ae22ULL,0x7137449123ef65cdULL,0xb5c0fbcfec4d3b2fULL,0xe9b5dba58189dbbcULL, + 0x3956c25bf348b538ULL,0x59f111f1b605d019ULL,0x923f82a4af194f9bULL,0xab1c5ed5da6d8118ULL, + 0xd807aa98a3030242ULL,0x12835b0145706fbeULL,0x243185be4ee4b28cULL,0x550c7dc3d5ffb4e2ULL, + 0x72be5d74f27b896fULL,0x80deb1fe3b1696b1ULL,0x9bdc06a725c71235ULL,0xc19bf174cf692694ULL, + 0xe49b69c19ef14ad2ULL,0xefbe4786384f25e3ULL,0x0fc19dc68b8cd5b5ULL,0x240ca1cc77ac9c65ULL, + 0x2de92c6f592b0275ULL,0x4a7484aa6ea6e483ULL,0x5cb0a9dcbd41fbd4ULL,0x76f988da831153b5ULL, + 0x983e5152ee66dfabULL,0xa831c66d2db43210ULL,0xb00327c898fb213fULL,0xbf597fc7beef0ee4ULL, + 0xc6e00bf33da88fc2ULL,0xd5a79147930aa725ULL,0x06ca6351e003826fULL,0x142929670a0e6e70ULL, + 0x27b70a8546d22ffcULL,0x2e1b21385c26c926ULL,0x4d2c6dfc5ac42aedULL,0x53380d139d95b3dfULL, + 0x650a73548baf63deULL,0x766a0abb3c77b2a8ULL,0x81c2c92e47edaee6ULL,0x92722c851482353bULL, + 0xa2bfe8a14cf10364ULL,0xa81a664bbc423001ULL,0xc24b8b70d0f89791ULL,0xc76c51a30654be30ULL, + 0xd192e819d6ef5218ULL,0xd69906245565a910ULL,0xf40e35855771202aULL,0x106aa07032bbd1b8ULL, + 0x19a4c116b8d2d0c8ULL,0x1e376c085141ab53ULL,0x2748774cdf8eeb99ULL,0x34b0bcb5e19b48a8ULL, + 0x391c0cb3c5c95a63ULL,0x4ed8aa4ae3418acbULL,0x5b9cca4f7763e373ULL,0x682e6ff3d6b2b8a3ULL, + 0x748f82ee5defb2fcULL,0x78a5636f43172f60ULL,0x84c87814a1f0ab72ULL,0x8cc702081a6439ecULL, + 0x90befffa23631e28ULL,0xa4506cebde82bde9ULL,0xbef9a3f7b2c67915ULL,0xc67178f2e372532bULL, + 0xca273eceea26619cULL,0xd186b8c721c0c207ULL,0xeada7dd6cde0eb1eULL,0xf57d4f7fee6ed178ULL, + 0x06f067aa72176fbaULL,0x0a637dc5a2c898a6ULL,0x113f9804bef90daeULL,0x1b710b35131c471bULL, + 0x28db77f523047d84ULL,0x32caab7b40c72493ULL,0x3c9ebe0a15c9bebcULL,0x431d67c49c100d4cULL, + 0x4cc5d4becb3e42b6ULL,0x597f299cfc657e2aULL,0x5fcb6fab3ad6faecULL,0x6c44198c4a475817ULL +}; + +#define STORE64H(x, y) Utils::storeBigEndian(y,x) +#define LOAD64H(x, y) x = Utils::loadBigEndian(y) +#define ROL64c(x,y) (((x)<<(y)) | ((x)>>(64-(y)))) +#define ROR64c(x,y) (((x)>>(y)) | ((x)<<(64-(y)))) +#define Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Maj(x,y,z) (((x | y) & z) | (x & y)) +#define S(x, n) ROR64c(x, n) +#define R(x, n) ((x)>>(n)) +#define Sigma0(x) (S(x, 28) ^ S(x, 34) ^ S(x, 39)) +#define Sigma1(x) (S(x, 14) ^ S(x, 18) ^ S(x, 41)) +#define Gamma0(x) (S(x, 1) ^ S(x, 8) ^ R(x, 7)) +#define Gamma1(x) (S(x, 19) ^ S(x, 61) ^ R(x, 6)) + +static ZT_INLINE void sha512_compress(sha512_state *const md,uint8_t *const buf) { - unsigned char h[64]; - unsigned char padded[256]; + uint64_t S[8], W[80], t0, t1; int i; - uint64_t bytes = len; - const unsigned char *in = (const unsigned char *)data; - unsigned int inlen = len; + for (i = 0; i < 8; i++) + S[i] = md->state[i]; + for (i = 0; i < 16; i++) + LOAD64H(W[i], buf + (8*i)); + for (i = 16; i < 80; i++) + W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]; - for (i = 0;i < 64;++i) h[i] = iv[i]; +#define RND(a,b,c,d,e,f,g,h,i) \ + t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ + t1 = Sigma0(a) + Maj(a, b, c); \ + d += t0; \ + h = t0 + t1; - blocks(h,in,inlen); - in += inlen; - inlen &= 127; - in -= inlen; - - for (i = 0;i < (int)inlen;++i) padded[i] = in[i]; - padded[inlen] = 0x80; - - if (inlen < 112) { - for (i = inlen + 1;i < 119;++i) padded[i] = 0; - padded[119] = (unsigned char)((bytes >> 61) & 0xff); - padded[120] = (unsigned char)((bytes >> 53) & 0xff); - padded[121] = (unsigned char)((bytes >> 45) & 0xff); - padded[122] = (unsigned char)((bytes >> 37) & 0xff); - padded[123] = (unsigned char)((bytes >> 29) & 0xff); - padded[124] = (unsigned char)((bytes >> 21) & 0xff); - padded[125] = (unsigned char)((bytes >> 13) & 0xff); - padded[126] = (unsigned char)((bytes >> 5) & 0xff); - padded[127] = (unsigned char)((bytes << 3) & 0xff); - blocks(h,padded,128); - } else { - for (i = inlen + 1;i < 247;++i) padded[i] = 0; - padded[247] = (unsigned char)((bytes >> 61) & 0xff); - padded[248] = (unsigned char)((bytes >> 53) & 0xff); - padded[249] = (unsigned char)((bytes >> 45) & 0xff); - padded[250] = (unsigned char)((bytes >> 37) & 0xff); - padded[251] = (unsigned char)((bytes >> 29) & 0xff); - padded[252] = (unsigned char)((bytes >> 21) & 0xff); - padded[253] = (unsigned char)((bytes >> 13) & 0xff); - padded[254] = (unsigned char)((bytes >> 5) & 0xff); - padded[255] = (unsigned char)((bytes << 3) & 0xff); - blocks(h,padded,256); + for (i = 0; i < 80; i += 8) { + RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i+0); + RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],i+1); + RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],i+2); + RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],i+3); + RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],i+4); + RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],i+5); + RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],i+6); + RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],i+7); } - for (i = 0;i < 64;++i) ((unsigned char *)digest)[i] = h[i]; + for (i = 0; i < 8; i++) + md->state[i] = md->state[i] + S[i]; } -} // namespace ZeroTier +static ZT_INLINE void sha384_init(sha512_state *const md) +{ + md->curlen = 0; + md->length = 0; + md->state[0] = 0xcbbb9d5dc1059ed8ULL; + md->state[1] = 0x629a292a367cd507ULL; + md->state[2] = 0x9159015a3070dd17ULL; + md->state[3] = 0x152fecd8f70e5939ULL; + md->state[4] = 0x67332667ffc00b31ULL; + md->state[5] = 0x8eb44a8768581511ULL; + md->state[6] = 0xdb0c2e0d64f98fa7ULL; + md->state[7] = 0x47b5481dbefa4fa4ULL; +} + +static ZT_INLINE void sha512_init(sha512_state *const md) +{ + md->curlen = 0; + md->length = 0; + md->state[0] = 0x6a09e667f3bcc908ULL; + md->state[1] = 0xbb67ae8584caa73bULL; + md->state[2] = 0x3c6ef372fe94f82bULL; + md->state[3] = 0xa54ff53a5f1d36f1ULL; + md->state[4] = 0x510e527fade682d1ULL; + md->state[5] = 0x9b05688c2b3e6c1fULL; + md->state[6] = 0x1f83d9abfb41bd6bULL; + md->state[7] = 0x5be0cd19137e2179ULL; +} + +static void sha512_process(sha512_state *const md,const uint8_t *in,unsigned long inlen) +{ + while (inlen > 0) { + if (md->curlen == 0 && inlen >= 128) { + sha512_compress(md,(uint8_t *)in); + md->length += 128 * 8; + in += 128; + inlen -= 128; + } else { + unsigned long n = std::min(inlen,(128 - md->curlen)); + Utils::copy(md->buf + md->curlen,in,n); + md->curlen += n; + in += n; + inlen -= n; + if (md->curlen == 128) { + sha512_compress(md,md->buf); + md->length += 8*128; + md->curlen = 0; + } + } + } +} + +static ZT_INLINE void sha512_done(sha512_state *const md,uint8_t *out) +{ + int i; + + md->length += md->curlen * 8ULL; + md->buf[md->curlen++] = (uint8_t)0x80; + + if (md->curlen > 112) { + while (md->curlen < 128) { + md->buf[md->curlen++] = (uint8_t)0; + } + sha512_compress(md, md->buf); + md->curlen = 0; + } + + while (md->curlen < 120) { + md->buf[md->curlen++] = (uint8_t)0; + } + + STORE64H(md->length, md->buf+120); + sha512_compress(md, md->buf); + + for (i = 0; i < 8; i++) { + STORE64H(md->state[i], out+(8*i)); + } +} + +} // anonymous namespace + +void SHA512(void *digest,const void *data,unsigned int len) +{ + sha512_state state; + sha512_init(&state); + sha512_process(&state,(uint8_t *)data,(unsigned long)len); + sha512_done(&state,(uint8_t *)digest); +} + +void SHA384(void *digest,const void *data,unsigned int len) +{ + uint8_t tmp[64]; + sha512_state state; + sha384_init(&state); + sha512_process(&state,(uint8_t *)data,(unsigned long)len); + sha512_done(&state,tmp); + Utils::copy<48>(digest,tmp); +} + +void SHA384(void *digest,const void *data0,unsigned int len0,const void *data1,unsigned int len1) +{ + uint8_t tmp[64]; + sha512_state state; + sha384_init(&state); + sha512_process(&state,(uint8_t *)data0,(unsigned long)len0); + sha512_process(&state,(uint8_t *)data1,(unsigned long)len1); + sha512_done(&state,tmp); + Utils::copy<48>(digest,tmp); +} #endif // !ZT_HAVE_NATIVE_SHA512 -// Internally re-export to included C code, which includes some fast crypto code ported in on some platforms. -// This eliminates the need to link against a third party SHA512() from this code -extern "C" void ZT_sha512internal(void *digest,const void *data,unsigned int len) +void HMACSHA384(const uint8_t key[ZT_SYMMETRIC_KEY_SIZE],const void *msg,const unsigned int msglen,uint8_t mac[48]) { - ZeroTier::SHA512::hash(digest,data,len); + uint64_t kInPadded[16]; // input padded key + uint64_t outer[22]; // output padded key | H(input padded key | msg) + + const uint64_t k0 = Utils::loadMachineEndian< uint64_t >(key); + const uint64_t k1 = Utils::loadMachineEndian< uint64_t >(key + 8); + const uint64_t k2 = Utils::loadMachineEndian< uint64_t >(key + 16); + const uint64_t k3 = Utils::loadMachineEndian< uint64_t >(key + 24); + const uint64_t k4 = Utils::loadMachineEndian< uint64_t >(key + 32); + const uint64_t k5 = Utils::loadMachineEndian< uint64_t >(key + 40); + + const uint64_t ipad = 0x3636363636363636ULL; + kInPadded[0] = k0 ^ ipad; + kInPadded[1] = k1 ^ ipad; + kInPadded[2] = k2 ^ ipad; + kInPadded[3] = k3 ^ ipad; + kInPadded[4] = k4 ^ ipad; + kInPadded[5] = k5 ^ ipad; + kInPadded[6] = ipad; + kInPadded[7] = ipad; + kInPadded[8] = ipad; + kInPadded[9] = ipad; + kInPadded[10] = ipad; + kInPadded[11] = ipad; + kInPadded[12] = ipad; + kInPadded[13] = ipad; + kInPadded[14] = ipad; + kInPadded[15] = ipad; + + const uint64_t opad = 0x5c5c5c5c5c5c5c5cULL; + outer[0] = k0 ^ opad; + outer[1] = k1 ^ opad; + outer[2] = k2 ^ opad; + outer[3] = k3 ^ opad; + outer[4] = k4 ^ opad; + outer[5] = k5 ^ opad; + outer[6] = opad; + outer[7] = opad; + outer[8] = opad; + outer[9] = opad; + outer[10] = opad; + outer[11] = opad; + outer[12] = opad; + outer[13] = opad; + outer[14] = opad; + outer[15] = opad; + + // H(output padded key | H(input padded key | msg)) + SHA384(reinterpret_cast(outer) + 128,kInPadded,128,msg,msglen); + SHA384(mac,outer,176); } + +void KBKDFHMACSHA384(const uint8_t key[ZT_SYMMETRIC_KEY_SIZE],const char label,const char context,const uint32_t iter,uint8_t out[ZT_SYMMETRIC_KEY_SIZE]) +{ + uint8_t kbkdfMsg[13]; + + Utils::storeBigEndian(kbkdfMsg,(uint32_t)iter); + + kbkdfMsg[4] = (uint8_t)'Z'; + kbkdfMsg[5] = (uint8_t)'T'; // preface our labels with something ZT-specific + kbkdfMsg[6] = (uint8_t)label; + kbkdfMsg[7] = 0; + + kbkdfMsg[8] = (uint8_t)context; + + // Output key length: 384 bits (as 32-bit big-endian value) + kbkdfMsg[9] = 0; + kbkdfMsg[10] = 0; + kbkdfMsg[11] = 0x01; + kbkdfMsg[12] = 0x80; + + static_assert(ZT_SYMMETRIC_KEY_SIZE == ZT_SHA384_DIGEST_SIZE,"sizeof(out) != ZT_SHA384_DIGEST_SIZE"); + HMACSHA384(key,&kbkdfMsg,sizeof(kbkdfMsg),out); +} + +} // namespace ZeroTier diff --git a/node/SHA512.hpp b/node/SHA512.hpp index eefaead16..12ac9583a 100644 --- a/node/SHA512.hpp +++ b/node/SHA512.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. @@ -14,18 +14,74 @@ #ifndef ZT_SHA512_HPP #define ZT_SHA512_HPP -#define ZT_SHA512_DIGEST_LEN 64 +#include "Constants.hpp" + +#ifdef __APPLE__ +#include +#endif + +#define ZT_SHA512_DIGEST_SIZE 64 +#define ZT_SHA384_DIGEST_SIZE 48 +#define ZT_SHA512_BLOCK_SIZE 128 +#define ZT_SHA384_BLOCK_SIZE 128 + +#define ZT_HMACSHA384_LEN 48 namespace ZeroTier { -/** - * SHA-512 digest algorithm - */ -class SHA512 +// SHA384 and SHA512 are actually in the standard libraries on MacOS and iOS +#ifdef __APPLE__ +#define ZT_HAVE_NATIVE_SHA512 1 +static ZT_INLINE void SHA512(void *digest,const void *data,unsigned int len) { -public: - static void hash(void *digest,const void *data,unsigned int len); -}; + CC_SHA512_CTX ctx; + CC_SHA512_Init(&ctx); + CC_SHA512_Update(&ctx,data,len); + CC_SHA512_Final(reinterpret_cast(digest),&ctx); +} +static ZT_INLINE void SHA384(void *digest,const void *data,unsigned int len) +{ + CC_SHA512_CTX ctx; + CC_SHA384_Init(&ctx); + CC_SHA384_Update(&ctx,data,len); + CC_SHA384_Final(reinterpret_cast(digest),&ctx); +} +static ZT_INLINE void SHA384(void *digest,const void *data0,unsigned int len0,const void *data1,unsigned int len1) +{ + CC_SHA512_CTX ctx; + CC_SHA384_Init(&ctx); + CC_SHA384_Update(&ctx,data0,len0); + CC_SHA384_Update(&ctx,data1,len1); + CC_SHA384_Final(reinterpret_cast(digest),&ctx); +} +#endif + +#ifndef ZT_HAVE_NATIVE_SHA512 +void SHA512(void *digest,const void *data,unsigned int len); +void SHA384(void *digest,const void *data,unsigned int len); +void SHA384(void *digest,const void *data0,unsigned int len0,const void *data1,unsigned int len1); +#endif + +/** + * Compute HMAC SHA-384 using a 256-bit key + * + * @param key Secret key + * @param msg Message to HMAC + * @param msglen Length of message + * @param mac Buffer to fill with result + */ +void HMACSHA384(const uint8_t key[ZT_SYMMETRIC_KEY_SIZE],const void *msg,unsigned int msglen,uint8_t mac[48]); + +/** + * Compute KBKDF (key-based key derivation function) using HMAC-SHA-384 as a PRF + * + * @param key Source master key + * @param label A label indicating the key's purpose in the ZeroTier system + * @param context An arbitrary "context" or zero if not applicable + * @param iter Key iteration for generation of multiple keys for the same label/context + * @param out Output to receive derived key + */ +void KBKDFHMACSHA384(const uint8_t key[ZT_SYMMETRIC_KEY_SIZE],char label,char context,uint32_t iter,uint8_t out[ZT_SYMMETRIC_KEY_SIZE]); } // namespace ZeroTier diff --git a/node/Switch.cpp b/node/Switch.cpp index c24e2d231..1e7e2ca23 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -96,7 +96,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre if ((now - _lastBeaconResponse) >= 2500) { // limit rate of responses _lastBeaconResponse = now; Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NOP); - outp.armor(peer->key(),true); + outp.armor(peer->key(),true,peer->aesKeysIfSupported()); path->send(RR,tPtr,outp.data(),outp.size(),now); } } @@ -879,7 +879,6 @@ void Switch::requestWhois(void *tPtr,const int64_t now,const Address &addr) int32_t flowId = ZT_QOS_NO_FLOW; Packet outp(upstream->address(),RR->identity.address(),Packet::VERB_WHOIS); addr.appendTo(outp); - RR->node->expectReplyTo(outp.packetId()); send(tPtr,outp,true,flowId); } } @@ -1042,14 +1041,16 @@ void Switch::_sendViaSpecificPath(void *tPtr,SharedPtr peer,SharedPtrrecordOutgoingPacket(viaPath, packet.packetId(), packet.payloadLength(), packet.verb(), flowId, now); - if (trustedPathId) { packet.setTrusted(trustedPathId); } else { - packet.armor(peer->key(),encrypt); + Packet::Verb v = packet.verb(); + packet.armor(peer->key(),encrypt,peer->aesKeysIfSupported()); + RR->node->expectReplyTo(packet.packetId()); } + peer->recordOutgoingPacket(viaPath, packet.packetId(), packet.payloadLength(), packet.verb(), flowId, now); + if (viaPath->send(RR,tPtr,packet.data(),chunkSize,now)) { if (chunkSize < packet.size()) { // Too big for one packet, fragment the rest diff --git a/service/SoftwareUpdater.cpp b/service/SoftwareUpdater.cpp index 6ace5fd84..8c76ab3eb 100644 --- a/service/SoftwareUpdater.cpp +++ b/service/SoftwareUpdater.cpp @@ -112,10 +112,10 @@ void SoftwareUpdater::setUpdateDistribution(bool distribute) // If update meta is called e.g. foo.exe.json, then foo.exe is the update itself const std::string binPath(udd + ZT_PATH_SEPARATOR_S + u->substr(0,u->length() - 5)); const std::string metaHash(OSUtils::jsonBinFromHex(d.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH])); - if ((metaHash.length() == ZT_SHA512_DIGEST_LEN)&&(OSUtils::readFile(binPath.c_str(),d.bin))) { - std::array sha512; - SHA512::hash(sha512.data(),d.bin.data(),(unsigned int)d.bin.length()); - if (!memcmp(sha512.data(),metaHash.data(),ZT_SHA512_DIGEST_LEN)) { // double check that hash in JSON is correct + if ((metaHash.length() == 64)&&(OSUtils::readFile(binPath.c_str(),d.bin))) { + std::array sha512; + SHA512(sha512.data(),d.bin.data(),(unsigned int)d.bin.length()); + if (!memcmp(sha512.data(),metaHash.data(),64)) { // double check that hash in JSON is correct d.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIZE] = d.bin.length(); // override with correct value -- setting this in meta json is optional std::array shakey; memcpy(shakey.data(),sha512.data(),16); @@ -333,10 +333,10 @@ bool SoftwareUpdater::check(const int64_t now) const std::string binPath(_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME); try { // (1) Check the hash itself to make sure the image is basically okay - uint8_t sha512[ZT_SHA512_DIGEST_LEN]; - SHA512::hash(sha512,_download.data(),(unsigned int)_download.length()); - char hexbuf[(ZT_SHA512_DIGEST_LEN * 2) + 2]; - if (OSUtils::jsonString(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH],"") == Utils::hex(sha512,ZT_SHA512_DIGEST_LEN,hexbuf)) { + uint8_t sha512[64]; + SHA512(sha512,_download.data(),(unsigned int)_download.length()); + char hexbuf[(64 * 2) + 2]; + if (OSUtils::jsonString(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH],"") == Utils::hex(sha512,64,hexbuf)) { // (2) Check signature by signing authority const std::string sig(OSUtils::jsonBinFromHex(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNATURE])); if (Identity(ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY).verify(_download.data(),(unsigned int)_download.length(),sig.data(),(unsigned int)sig.length())) { From 7f99c4a7793b8c0621b12f0f1ad57d62b0b3dbc9 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Fri, 21 Aug 2020 16:22:28 -0700 Subject: [PATCH 131/362] Sleep 10 seconds between writes to DB --- controller/PostgreSQL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 0ea404559..1e25abbcd 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1714,7 +1714,7 @@ void PostgreSQL::onlineNotification_Redis() fprintf(stderr, "Error in online notification thread (redis): %s\n", e.what()); #endif } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::seconds(10)); } } From b1ddba0438bc3b33ebc0e28ac5c015fa63be1430 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 24 Aug 2020 18:56:49 -0700 Subject: [PATCH 132/362] Remove a few old comments --- node/Bond.hpp | 4 +++- service/OneService.cpp | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/node/Bond.hpp b/node/Bond.hpp index c8a89b616..b655f47ae 100644 --- a/node/Bond.hpp +++ b/node/Bond.hpp @@ -87,7 +87,9 @@ public: std::string policyAlias() { return _policyAlias; } /** - * Inform the bond about the path that its peer (owning object) just learned about + * Inform the bond about the path that its peer (owning object) just learned about. + * If the path is allowed to be used, it will be inducted into the bond on a trial + * period where link statistics will be collected to judge its quality. * * @param path Newly-learned Path which should now be handled by the Bond * @param now Current time diff --git a/service/OneService.cpp b/service/OneService.cpp index 076dbb59d..1c5dbc3f7 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -1630,7 +1630,6 @@ public: // Custom Policies json &customBondingPolicies = settings["policies"]; for (json::iterator policyItr = customBondingPolicies.begin(); policyItr != customBondingPolicies.end();++policyItr) { - //fprintf(stderr, "\n\n--- (%s)\n", policyItr.key().c_str()); // Custom Policy std::string customPolicyStr(policyItr.key()); json &customPolicy = policyItr.value(); @@ -1684,7 +1683,6 @@ public: // Policy-Specific link set json &links = customPolicy["links"]; for (json::iterator linkItr = links.begin(); linkItr != links.end();++linkItr) { - //fprintf(stderr, "\t--- link (%s)\n", linkItr.key().c_str()); std::string linkNameStr(linkItr.key()); json &link = linkItr.value(); @@ -1719,12 +1717,19 @@ public: } _node->bondController()->addCustomLink(customPolicyStr, new Link(linkNameStr,ipvPref,speed,linkMonitorInterval,upDelay,downDelay,enabled,linkMode,failoverToStr,alloc)); } - // TODO: This is dumb std::string linkSelectMethodStr(OSUtils::jsonString(customPolicy["activeReselect"],"optimize")); - if (linkSelectMethodStr == "always") { newTemplateBond->setLinkSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_ALWAYS); } - if (linkSelectMethodStr == "better") { newTemplateBond->setLinkSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_BETTER); } - if (linkSelectMethodStr == "failure") { newTemplateBond->setLinkSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_FAILURE); } - if (linkSelectMethodStr == "optimize") { newTemplateBond->setLinkSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE); } + if (linkSelectMethodStr == "always") { + newTemplateBond->setLinkSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_ALWAYS); + } + if (linkSelectMethodStr == "better") { + newTemplateBond->setLinkSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_BETTER); + } + if (linkSelectMethodStr == "failure") { + newTemplateBond->setLinkSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_FAILURE); + } + if (linkSelectMethodStr == "optimize") { + newTemplateBond->setLinkSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE); + } if (newTemplateBond->getLinkSelectMethod() < 0 || newTemplateBond->getLinkSelectMethod() > 3) { fprintf(stderr, "warning: invalid value (%s) for linkSelectMethod, assuming mode: always\n", linkSelectMethodStr.c_str()); } From 93d6b41898665b6df42b23d0a04b2b5e34668f12 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 25 Aug 2020 08:08:54 -0700 Subject: [PATCH 133/362] Disable AES in commit so as not to break other builds. --- node/Peer.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/node/Peer.hpp b/node/Peer.hpp index 4c8865c65..dd362d697 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -534,7 +534,10 @@ public: inline int8_t bondingPolicy() { return _bondingPolicy; } const AES *aesKeysIfSupported() const - { return (_vProto >= 10) ? _aesKeys : (const AES *)0; } + { return (const AES *)0; } + + //const AES *aesKeysIfSupported() const + //{ return (_vProto >= 10) ? _aesKeys : (const AES *)0; } private: struct _PeerPath From b7b01da742fefe2a8a124a555012f41505f69ce7 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 25 Aug 2020 14:13:20 -0700 Subject: [PATCH 134/362] Wire up dearmor() path. --- node/IncomingPacket.cpp | 8 ++++---- node/Packet.cpp | 39 +++++++++++++++++++++++++++++---------- node/Packet.hpp | 5 +++-- node/Peer.hpp | 8 ++++---- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 9ff6edd58..56f1a5732 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -67,7 +67,7 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr,int32_t f const SharedPtr peer(RR->topology->getPeer(tPtr,sourceAddress)); if (peer) { if (!trusted) { - if (!dearmor(peer->key())) { + if (!dearmor(peer->key(), peer->aesKeysIfSupported())) { RR->t->incomingPacketMessageAuthenticationFailure(tPtr,_path,packetId(),sourceAddress,hops(),"invalid MAC"); peer->recordIncomingInvalidPacket(_path); return true; @@ -288,7 +288,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]; if (RR->identity.agree(id,key,ZT_PEER_SECRET_KEY_LENGTH)) { - if (dearmor(key)) { // ensure packet is authentic, otherwise drop + if (dearmor(key, peer->aesKeysIfSupported())) { // ensure packet is authentic, otherwise drop RR->t->incomingPacketDroppedHELLO(tPtr,_path,pid,fromAddress,"address collision"); Packet outp(id.address(),RR->identity.address(),Packet::VERB_ERROR); outp.append((uint8_t)Packet::VERB_HELLO); @@ -307,7 +307,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool } else { // Identity is the same as the one we already have -- check packet integrity - if (!dearmor(peer->key())) { + if (!dearmor(peer->key(), peer->aesKeysIfSupported())) { RR->t->incomingPacketMessageAuthenticationFailure(tPtr,_path,pid,fromAddress,hops(),"invalid MAC"); return true; } @@ -332,7 +332,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool // Check packet integrity and MAC (this is faster than locallyValidate() so do it first to filter out total crap) SharedPtr newPeer(new Peer(RR,RR->identity,id)); - if (!dearmor(newPeer->key())) { + if (!dearmor(newPeer->key(), newPeer->aesKeysIfSupported())) { RR->t->incomingPacketMessageAuthenticationFailure(tPtr,_path,pid,fromAddress,hops(),"invalid MAC"); return true; } diff --git a/node/Packet.cpp b/node/Packet.cpp index 04d8f92e5..8cc0f47c7 100644 --- a/node/Packet.cpp +++ b/node/Packet.cpp @@ -901,14 +901,16 @@ void Packet::armor(const void *key,bool encryptPayload,const AES aesKeys[2]) *reinterpret_cast(data + ZT_PACKET_IDX_MAC) = tag[1]; #endif } else { - uint8_t mangledKey[32]; setCipher(encryptPayload ? ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012 : ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE); + + uint8_t mangledKey[32]; _salsa20MangleKey((const unsigned char *)key,mangledKey); + if (ZT_HAS_FAST_CRYPTO()) { - const unsigned int encryptLen = (encryptPayload) ? (size() - ZT_PACKET_IDX_VERB) : 0; + const unsigned int payloadLen = (encryptPayload) ? (size() - ZT_PACKET_IDX_VERB) : 0; uint64_t keyStream[(ZT_PROTO_MAX_PACKET_LENGTH + 64 + 8) / 8]; - ZT_FAST_SINGLE_PASS_SALSA2012(keyStream,encryptLen + 64,(data + ZT_PACKET_IDX_IV),mangledKey); - Salsa20::memxor(data + ZT_PACKET_IDX_VERB,reinterpret_cast(keyStream + 8),encryptLen); + ZT_FAST_SINGLE_PASS_SALSA2012(keyStream,payloadLen + 64,(data + ZT_PACKET_IDX_IV),mangledKey); + Salsa20::memxor(data + ZT_PACKET_IDX_VERB,reinterpret_cast(keyStream + 8),payloadLen); uint64_t mac[2]; Poly1305::compute(mac,data + ZT_PACKET_IDX_VERB,size() - ZT_PACKET_IDX_VERB,keyStream); #ifdef ZT_NO_TYPE_PUNNING @@ -934,15 +936,33 @@ void Packet::armor(const void *key,bool encryptPayload,const AES aesKeys[2]) } } -bool Packet::dearmor(const void *key) +bool Packet::dearmor(const void *key,const AES aesKeys[2]) { - uint8_t mangledKey[32]; uint8_t *const data = reinterpret_cast(unsafeData()); const unsigned int payloadLen = size() - ZT_PACKET_IDX_VERB; unsigned char *const payload = data + ZT_PACKET_IDX_VERB; const unsigned int cs = cipher(); - if ((cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE)||(cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012)) { + if (cs == ZT_PROTO_CIPHER_SUITE__AES_GMAC_SIV) { + if (aesKeys) { + AES::GMACSIVDecryptor dec(aesKeys[0],aesKeys[1]); + + uint64_t tag[2]; +#ifdef ZT_NO_UNALIGNED_ACCESS + Utils::copy<8>(tag, data); + Utils::copy<8>(tag + 1, data + ZT_PACKET_IDX_MAC); +#else + tag[0] = *reinterpret_cast(data); + tag[1] = *reinterpret_cast(data + ZT_PACKET_IDX_MAC); +#endif + + dec.init(tag, payload); + dec.aad(data + ZT_PACKET_IDX_DEST,11); + dec.update(payload, payloadLen); + return dec.finish(); + } + } else if ((cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE)||(cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012)) { + uint8_t mangledKey[32]; _salsa20MangleKey((const unsigned char *)key,mangledKey); if (ZT_HAS_FAST_CRYPTO()) { uint64_t keyStream[(ZT_PROTO_MAX_PACKET_LENGTH + 64 + 8) / 8]; @@ -974,11 +994,10 @@ bool Packet::dearmor(const void *key) if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) s20.crypt12(payload,payload,payloadLen); } - return true; - } else { - return false; // unrecognized cipher suite } + + return false; } void Packet::cryptField(const void *key,unsigned int start,unsigned int len) diff --git a/node/Packet.hpp b/node/Packet.hpp index 9089e5264..b12ca4b6e 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -1313,7 +1313,7 @@ public: * * @param key 32-byte key * @param encryptPayload If true, encrypt packet payload, else just MAC - * @param aes Use new AES-GMAC-SIV constrution + * @param aesKeys If non-NULL these are the two keys for AES-GMAC-SIV */ void armor(const void *key,bool encryptPayload,const AES aesKeys[2]); @@ -1325,9 +1325,10 @@ public: * address and MAC field match a trusted path. * * @param key 32-byte key + * @param aesKeys If non-NULL these are the two keys for AES-GMAC-SIV * @return False if packet is invalid or failed MAC authenticity check */ - bool dearmor(const void *key); + bool dearmor(const void *key,const AES aesKeys[2]); /** * Encrypt/decrypt a separately armored portion of a packet diff --git a/node/Peer.hpp b/node/Peer.hpp index dd362d697..63cbdbedc 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -533,11 +533,11 @@ public: */ inline int8_t bondingPolicy() { return _bondingPolicy; } - const AES *aesKeysIfSupported() const - { return (const AES *)0; } - //const AES *aesKeysIfSupported() const - //{ return (_vProto >= 10) ? _aesKeys : (const AES *)0; } + //{ return (const AES *)0; } + + const AES *aesKeysIfSupported() const + { return (_vProto >= 10) ? _aesKeys : (const AES *)0; } private: struct _PeerPath From 45b6d11126da8932fe1a15bcf35095f296fd13ad Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 26 Aug 2020 14:52:23 -0700 Subject: [PATCH 135/362] Put upserts from controller into single transaction --- controller/PostgreSQL.cpp | 75 ++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 1e25abbcd..d3a6b5bef 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1079,7 +1079,17 @@ void PostgreSQL::commitThread() vproto.c_str() }; - PGresult *res = PQexecParams(conn, + PGresult *res = PQexec(conn, "BEGIN"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + fprintf(stderr, "ERROR: Error beginning update transaction: %s\n", PQresultErrorMessage(res)); + PQclear(res); + delete config; + config = nullptr; + continue; + } + + + res = PQexecParams(conn, "INSERT INTO ztc_member (id, network_id, active_bridge, authorized, capabilities, " "identity, last_authorized_time, last_deauthorized_time, no_auto_assign_ips, " "remote_trace_level, remote_trace_target, revision, tags, v_major, v_minor, v_rev, v_proto) " @@ -1103,17 +1113,7 @@ void PostgreSQL::commitThread() fprintf(stderr, "ERROR: Error updating member: %s\n", PQresultErrorMessage(res)); fprintf(stderr, "%s", OSUtils::jsonDump(*config, 2).c_str()); PQclear(res); - delete config; - config = nullptr; - continue; - } - - PQclear(res); - - res = PQexec(conn, "BEGIN"); - if (PQresultStatus(res) != PGRES_COMMAND_OK) { - fprintf(stderr, "ERROR: Error beginning transaction: %s\n", PQresultErrorMessage(res)); - PQclear(res); + PQclear(PQexec(conn, "ROLLBACK")); delete config; config = nullptr; continue; @@ -1147,6 +1147,7 @@ void PostgreSQL::commitThread() PQclear(res); std::vector assignments; + bool ipAssignError = false; for (auto i = (*config)["ipAssignments"].begin(); i != (*config)["ipAssignments"].end(); ++i) { std::string addr = *i; @@ -1173,18 +1174,28 @@ void PostgreSQL::commitThread() fprintf(stderr, "ERROR: Error setting IP addresses for member: %s\n", PQresultErrorMessage(res)); PQclear(res); PQclear(PQexec(conn, "ROLLBACK")); - break;; + ipAssignError = true; + break; } + PQclear(res); assignments.push_back(addr); } + if (ipAssignError) { + delete config; + config = nullptr; + continue; + } res = PQexec(conn, "COMMIT"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { - fprintf(stderr, "ERROR: Error committing ip address data: %s\n", PQresultErrorMessage(res)); + fprintf(stderr, "ERROR: Error committing member transaction: %s\n", PQresultErrorMessage(res)); + PQclear(res); + PQclear(PQexec(conn, "ROLLBACK")); + delete config; + config = nullptr; + continue; } - PQclear(res); - const uint64_t nwidInt = OSUtils::jsonIntHex((*config)["nwid"], 0ULL); const uint64_t memberidInt = OSUtils::jsonIntHex((*config)["id"], 0ULL); if (nwidInt && memberidInt) { @@ -1247,13 +1258,24 @@ void PostgreSQL::commitThread() v6mode.c_str(), }; + PGresult *res = PQexec(conn, "BEGIN"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + fprintf(stderr, "ERROR: Error beginnning transaction: %s\n", PQresultErrorMessage(res)); + PQclear(res); + delete config; + config = nullptr; + continue; + } + + PQclear(res); + // This ugly query exists because when we want to mirror networks to/from // another data store (e.g. FileDB or LFDB) it is possible to get a network // that doesn't exist in Central's database. This does an upsert and sets // the owner_id to the "first" global admin in the user DB if the record // did not previously exist. If the record already exists owner_id is left // unchanged, so owner_id should be left out of the update clause. - PGresult *res = PQexecParams(conn, + res = PQexecParams(conn, "INSERT INTO ztc_network (id, creation_time, owner_id, controller_id, capabilities, enable_broadcast, " "last_modified, mtu, multicast_limit, name, private, " "remote_trace_level, remote_trace_target, rules, rules_source, " @@ -1280,24 +1302,14 @@ void PostgreSQL::commitThread() if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "ERROR: Error updating network record: %s\n", PQresultErrorMessage(res)); PQclear(res); + PQclear(PQexec(conn, "ROLLBACK")); delete config; config = nullptr; continue; } PQclear(res); - - res = PQexec(conn, "BEGIN"); - if (PQresultStatus(res) != PGRES_COMMAND_OK) { - fprintf(stderr, "ERROR: Error beginnning transaction: %s\n", PQresultErrorMessage(res)); - PQclear(res); - delete config; - config = nullptr; - continue; - } - - PQclear(res); - + const char *params[1] = { id.c_str() }; @@ -1428,6 +1440,11 @@ void PostgreSQL::commitThread() res = PQexec(conn, "COMMIT"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "ERROR: Error committing network update: %s\n", PQresultErrorMessage(res)); + PQclear(res); + PQclear(PQexec(conn, "ROLLBACK")); + delete config; + config = nullptr; + continue; } PQclear(res); From 43c108f077cb01f48e043e2fef52a0b67a3548b3 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 26 Aug 2020 15:50:36 -0700 Subject: [PATCH 136/362] missed a rollback --- controller/PostgreSQL.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 43b15992c..b0709ad1c 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1399,6 +1399,7 @@ void PostgreSQL::commitThread() PQclear(res); } if (err) { + PQclear(res); PQclear(PQexec(conn, "ROLLBACK")); delete config; config = nullptr; @@ -1502,6 +1503,7 @@ void PostgreSQL::commitThread() if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "ERROR: Error updating DNS: %s\n", PQresultErrorMessage(res)); PQclear(res); + PQclear(PQexec(conn, "ROLLBACK")); err = true; break; } From 53d5c9890f94d32715b637a650f09da0e4a7f936 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 26 Aug 2020 15:50:36 -0700 Subject: [PATCH 137/362] missed a rollback # Conflicts: # controller/PostgreSQL.cpp --- controller/PostgreSQL.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index d3a6b5bef..3491e2106 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -4,7 +4,7 @@ * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2025-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -1361,6 +1361,7 @@ void PostgreSQL::commitThread() PQclear(res); } if (err) { + PQclear(res); PQclear(PQexec(conn, "ROLLBACK")); delete config; config = nullptr; From a1f439795900d42d2fa0c5a4fb87e501d045102c Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Sun, 30 Aug 2020 14:19:56 -0700 Subject: [PATCH 138/362] linux implementaiton of zerotier-cli dump --- one.cpp | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/one.cpp b/one.cpp index f582f94f9..d8a53dcd4 100644 --- a/one.cpp +++ b/one.cpp @@ -47,6 +47,11 @@ #include #include #include +#include +#include +#include +#include +#include #ifndef ZT_NO_CAPABILITIES #include #include @@ -1044,9 +1049,81 @@ static int cli(int argc,char **argv) free(addresses); addresses = NULL; } +#elif defined(__LINUX__) + struct ifreq ifr; + struct ifconf ifc; + char buf[1024]; + char stringBuffer[128]; + int success = 0; + + int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + ioctl(sock, SIOCGIFCONF, &ifc); + + struct ifreq *it = ifc.ifc_req; + const struct ifreq * const end = it + (ifc.ifc_len / sizeof(struct ifreq)); + int count = 0; + for(; it != end; ++it) { + strcpy(ifr.ifr_name, it->ifr_name); + if(ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) { + if (!(ifr.ifr_flags & IFF_LOOPBACK)) { // skip loopback + dump << "Interface " << count++ << ZT_EOL_S << "-----------" << ZT_EOL_S; + dump << "Name: " << ifr.ifr_name << ZT_EOL_S; + if (ioctl(sock, SIOCGIFMTU, &ifr) == 0) { + dump << "MTU: " << ifr.ifr_mtu << ZT_EOL_S; + } + if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) { + unsigned char mac_addr[6]; + memcpy(mac_addr, ifr.ifr_hwaddr.sa_data, 6); + char macStr[16]; + sprintf(macStr, "%02x:%02x:%02x:%02x:%02x:%02x", + mac_addr[0], + mac_addr[1], + mac_addr[2], + mac_addr[3], + mac_addr[4], + mac_addr[5]); + dump << "MAC: " << macStr << ZT_EOL_S; + } + + dump << "Addresses: " << ZT_EOL_S; + struct ifaddrs *ifap, *ifa; + void *addr; + getifaddrs(&ifap); + for(ifa = ifap; ifa; ifa = ifa->ifa_next) { + if(strcmp(ifr.ifr_name, ifa->ifa_name) == 0) { + if(ifa->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in *ipv4 = (struct sockaddr_in*)ifa->ifa_addr; + addr = &ipv4->sin_addr; + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6*)ifa->ifa_addr; + addr = &ipv6->sin6_addr; + } else { + continue; + } + inet_ntop(ifa->ifa_addr->sa_family, addr, stringBuffer, sizeof(stringBuffer)); + dump << stringBuffer << ZT_EOL_S; + } + } + } + } + } + close(sock); + char cwd[PATH_MAX]; + getcwd(cwd, sizeof(cwd)); + sprintf(cwd, "%s%szerotier_dump.txt", cwd, ZT_PATH_SEPARATOR_S); + fprintf(stdout, "Writing dump to: %s\n", cwd); + int fd = open(cwd, O_CREAT|O_RDWR,0664); + if (fd == -1) { + fprintf(stderr, "Error creating file.\n"); + } + write(fd, dump.str().c_str(), dump.str().size()); + close(fd); #endif - fprintf(stderr, "%s\n", dump.str().c_str()); + // fprintf(stderr, "%s\n", dump.str().c_str()); } else { cliPrintHelp(argv[0],stderr); From 2d7a96416cf391fc4ac1771fe246881e937b81b0 Mon Sep 17 00:00:00 2001 From: Michael Adams Date: Tue, 8 Sep 2020 12:25:40 -0700 Subject: [PATCH 139/362] Update issue templates Include use of Discuss forum as an option for feature requests. --- .github/ISSUE_TEMPLATE/bug_report.md | 5 ++++- .github/ISSUE_TEMPLATE/feature_request.md | 15 ++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 92a267422..8da91fdf0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,8 +1,12 @@ --- name: Bug report about: Create a report to help us improve +title: '' +labels: '' +assignees: '' --- + **Alternative, faster ways to get help** If you have just started using ZeroTier, here are some places to get help: - my.zerotier.com has a _Community_ tab. It's a live chat with other users and the developers. @@ -43,4 +47,3 @@ Add any other context about the problem here. - Router Config - Firewall Config (try turning the firewall off) - General Network Environment: [ e.g Home, University Campus, Corporate LAN ] - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 066b2d920..d4a745fcd 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,17 +1,14 @@ --- name: Feature request about: Suggest an idea for this project +title: "[Feature Request] " +labels: suggestion +assignees: '' --- -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] +If there is something you'd like to have added to ZeroTier, but not sure how to implement it, you might want to go to https://discuss.zerotier.com/c/feature-requests/ instead. Issues there can be voted on and discussed in-depth. -**Describe the solution you'd like** -A clear and concise description of what you want to happen. +If you do have some code or technical examples of how you want something done in ZeroTier, you can open an issue here that we can use alongside some pull requests. -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. +Thank you! From 8374553b6a86bbc3fca066b3825909402724d910 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 10 Sep 2020 14:36:33 -0400 Subject: [PATCH 140/362] Linux build fix for AES flags. --- make-linux.mk | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/make-linux.mk b/make-linux.mk index 6db07bada..71ead3650 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -65,8 +65,8 @@ ifeq ($(ZT_SANITIZE),1) SANFLAGS+=-fsanitize=address -DASAN_OPTIONS=symbolize=1 endif ifeq ($(ZT_DEBUG),1) - override CFLAGS+=-Wall -Wno-deprecated -g -pthread $(INCLUDES) $(DEFS) - override CXXFLAGS+=-Wall -Wno-deprecated -g -std=c++11 -pthread $(INCLUDES) $(DEFS) + override CFLAGS+=-Wall -Wno-deprecated -g -O -pthread $(INCLUDES) $(DEFS) + override CXXFLAGS+=-Wall -Wno-deprecated -g -O -std=c++11 -pthread $(INCLUDES) $(DEFS) ZT_TRACE=1 STRIP?=echo # The following line enables optimization for the crypto code, since @@ -112,12 +112,6 @@ ifeq ($(ZT_VAULT_SUPPORT),1) override LDLIBS+=-lcurl endif -# Uncomment for gprof profile build -#CFLAGS=-Wall -g -pg -pthread $(INCLUDES) $(DEFS) -#CXXFLAGS=-Wall -g -pg -pthread $(INCLUDES) $(DEFS) -#LDFLAGS= -#STRIP=echo - # Determine system build architecture from compiler target CC_MACH=$(shell $(CC) -dumpmachine | cut -d '-' -f 1) ZT_ARCHITECTURE=999 @@ -125,13 +119,15 @@ ifeq ($(CC_MACH),x86_64) ZT_ARCHITECTURE=2 ZT_USE_X64_ASM_SALSA=1 ZT_USE_X64_ASM_ED25519=1 - override CFLAGS+=-msse -msse2 -mssse3 -msse4 -msse4.1 -maes -mpclmul + override CFLAGS+=-msse -msse2 -mssse3 -msse4 -msse4.1 -msse4.2 -maes -mpclmul + override CXXFLAGS+=-msse -msse2 -mssse3 -msse4 -msse4.1 -msse4.2 -maes -mpclmul endif ifeq ($(CC_MACH),amd64) ZT_ARCHITECTURE=2 ZT_USE_X64_ASM_SALSA=1 ZT_USE_X64_ASM_ED25519=1 - override CFLAGS+=-msse -msse2 -mssse3 -msse4 -msse4.1 -maes -mpclmul + override CFLAGS+=-msse -msse2 -mssse3 -msse4 -msse4.1 -msse4.2 -maes -mpclmul + override CXXFLAGS+=-msse -msse2 -mssse3 -msse4 -msse4.1 -msse4.2 -maes -mpclmul endif ifeq ($(CC_MACH),powerpc64le) ZT_ARCHITECTURE=8 From 1ad555a0711b43477be5d58f174b6850fd1830e9 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 10 Sep 2020 14:48:48 -0400 Subject: [PATCH 141/362] More selective push of AES modifications and refactoring stuff. --- node/SHA512.cpp | 5 +++++ selftest.cpp | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/node/SHA512.cpp b/node/SHA512.cpp index ee28ed8bc..5c602673b 100644 --- a/node/SHA512.cpp +++ b/node/SHA512.cpp @@ -272,3 +272,8 @@ void KBKDFHMACSHA384(const uint8_t key[ZT_SYMMETRIC_KEY_SIZE],const char label,c } } // namespace ZeroTier + +// Internally re-export to included C code, which includes some fast crypto code ported in on some platforms. +// This eliminates the need to link against a third party SHA512() from this code +extern "C" void ZT_sha512internal(void *digest,const void *data,unsigned int len) +{ ZeroTier::SHA512(digest,data,len); } diff --git a/selftest.cpp b/selftest.cpp index 73281e6a1..926a6ebc2 100644 --- a/selftest.cpp +++ b/selftest.cpp @@ -198,7 +198,7 @@ static int testCrypto() bytes += 1234567.0; } uint64_t end = OSUtils::now(); - SHA512::hash(buf1,bb,1234567); + SHA512(buf1,bb,1234567); std::cout << ((bytes / 1048576.0) / ((long double)(end - start) / 1024.0)) << " MiB/second (" << Utils::hex(buf1,16,hexbuf) << ')' << std::endl; ::free((void *)bb); } @@ -250,13 +250,13 @@ static int testCrypto() bytes += 1234567.0; } uint64_t end = OSUtils::now(); - SHA512::hash(buf1,bb,1234567); + SHA512(buf1,bb,1234567); std::cout << ((bytes / 1048576.0) / ((long double)(end - start) / 1024.0)) << " MiB/second (" << Utils::hex(buf1,16,hexbuf) << ')' << std::endl; ::free((void *)bb); } std::cout << "[crypto] Testing SHA-512... "; std::cout.flush(); - SHA512::hash(buf1,sha512TV0Input,(unsigned int)strlen(sha512TV0Input)); + SHA512(buf1,sha512TV0Input,(unsigned int)strlen(sha512TV0Input)); if (memcmp(buf1,sha512TV0Digest,64)) { std::cout << "FAIL" << std::endl; return -1; @@ -617,8 +617,8 @@ static int testPacket() return -1; } - a.armor(salsaKey,true); - if (!a.dearmor(salsaKey)) { + a.armor(salsaKey,true,nullptr); + if (!a.dearmor(salsaKey,nullptr)) { std::cout << "FAIL (encrypt-decrypt/verify)" << std::endl; return -1; } From 0545e70bd58051a048730543684d3ac06cedfd09 Mon Sep 17 00:00:00 2001 From: Michael Adams Date: Thu, 10 Sep 2020 12:42:11 -0700 Subject: [PATCH 142/362] Added error messages to use admin/sudo/root --- .gitignore | 2 +- one.cpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 584bdb6c1..2143346ff 100755 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,6 @@ Thumbs.db /ext/installfiles/windows/Prerequisites /ext/installfiles/windows/*-cache /ZeroTier One.msi -/windows/.vs *.vcxproj.backup /windows/TapDriver6/Win7Debug /windows/TapDriver6/win7Release @@ -42,6 +41,7 @@ Thumbs.db enc_temp_folder /windows/copyutil/bin /windows/copyutil/obj +.vs/ # *nix/Mac build droppings /build-* diff --git a/one.cpp b/one.cpp index 20cdebfed..8b5ed4cf7 100644 --- a/one.cpp +++ b/one.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. @@ -85,7 +85,7 @@ using namespace ZeroTier; static OneService *volatile zt1Service = (OneService *)0; #define PROGRAM_NAME "ZeroTier One" -#define COPYRIGHT_NOTICE "Copyright (c) 2019 ZeroTier, Inc." +#define COPYRIGHT_NOTICE "Copyright (c) 2020 ZeroTier, Inc." #define LICENSE_GRANT "Licensed under the ZeroTier BSL 1.1 (see LICENSE.txt)" /****************************************************************************/ @@ -232,9 +232,12 @@ static int cli(int argc,char **argv) if (!homeDir.length()) homeDir = OneService::platformDefaultHomePath(); + // TODO: cleanup this logic + // A lot of generic CLI errors land here; missing admin rights cause a bit of this. if ((!port)||(!authToken.length())) { if (!homeDir.length()) { fprintf(stderr,"%s: missing port or authentication token and no home directory specified to auto-detect" ZT_EOL_S,argv[0]); + fprintf(stderr, "If you did not, please run this command as an Administrator / sudo / Root user. Thanks!"); return 2; } @@ -244,6 +247,7 @@ static int cli(int argc,char **argv) port = Utils::strToUInt(portStr.c_str()); if ((port == 0)||(port > 0xffff)) { fprintf(stderr,"%s: missing port and zerotier-one.port not found in %s" ZT_EOL_S,argv[0],homeDir.c_str()); + fprintf(stderr, "If you did not, please run this command as an Administrator / sudo / Root user. Thanks!"); return 2; } } @@ -266,6 +270,7 @@ static int cli(int argc,char **argv) #endif if (!authToken.length()) { fprintf(stderr,"%s: missing authentication token and authtoken.secret not found (or readable) in %s" ZT_EOL_S,argv[0],homeDir.c_str()); + fprintf(stderr, "If you did not, please run this command as an Administrator / sudo / Root user. Thanks!"); return 2; } } @@ -1316,6 +1321,7 @@ static BOOL WINAPI _winConsoleCtrlHandler(DWORD dwCtrlType) return FALSE; } +// TODO: revisit this with https://support.microsoft.com/en-us/help/947709/how-to-use-the-netsh-advfirewall-firewall-context-instead-of-the-netsh static void _winPokeAHole() { char myPath[MAX_PATH]; @@ -1690,7 +1696,7 @@ int main(int argc,char **argv) ptmp.append(*pi); if ((*pi != ".")&&(*pi != "..")) { if (!OSUtils::mkdir(ptmp)) - throw std::runtime_error("home path does not exist, and could not create"); + throw std::runtime_error("home path does not exist, and could not create. Please verify local system permissions."); } } } From e6b5f8aabd5e0acec1a9735095eec590c7d94bd4 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 10 Sep 2020 15:43:40 -0400 Subject: [PATCH 143/362] AES work... but disabled in this commit. --- node/Packet.cpp | 3 +++ node/Packet.hpp | 5 +++-- node/Peer.cpp | 4 ++-- node/Peer.hpp | 8 ++++---- node/Topology.cpp | 14 ++++++++------ node/Utils.hpp | 47 +++++++++++++++++++++++++++++++++++++++++++++++ version.h | 2 +- 7 files changed, 68 insertions(+), 15 deletions(-) diff --git a/node/Packet.cpp b/node/Packet.cpp index 8cc0f47c7..94168a503 100644 --- a/node/Packet.cpp +++ b/node/Packet.cpp @@ -880,6 +880,8 @@ void Packet::armor(const void *key,bool encryptPayload,const AES aesKeys[2]) { uint8_t *const data = reinterpret_cast(unsafeData()); if ((aesKeys) && (encryptPayload)) { + char tmp0[16],tmp1[16]; + printf("AES armor %.16llx %s -> %s %u\n",*reinterpret_cast(data),Address(data + ZT_PACKET_IDX_SOURCE,5).toString(tmp0),Address(data + ZT_PACKET_IDX_DEST,5).toString(tmp1),size()); setCipher(ZT_PROTO_CIPHER_SUITE__AES_GMAC_SIV); uint8_t *const payload = data + ZT_PACKET_IDX_VERB; @@ -945,6 +947,7 @@ bool Packet::dearmor(const void *key,const AES aesKeys[2]) if (cs == ZT_PROTO_CIPHER_SUITE__AES_GMAC_SIV) { if (aesKeys) { + printf("AES dearmor\n"); AES::GMACSIVDecryptor dec(aesKeys[0],aesKeys[1]); uint64_t tag[2]; diff --git a/node/Packet.hpp b/node/Packet.hpp index b12ca4b6e..78846ecdd 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -57,11 +57,12 @@ * + Inline push of CertificateOfMembership deprecated * 9 - 1.2.0 ... 1.2.14 * 10 - 1.4.0 ... 1.4.6 - * 11 - 1.4.8 ... end of 1.4 series + * 11 - 1.4.7 ... 1.4.8 * + Multipath capability and load balancing (beta) + * 12 - 1.4.8 ... CURRENT (1.4 series) * + AES-GMAC-SIV backported for faster peer-to-peer crypto */ -#define ZT_PROTO_VERSION 11 +#define ZT_PROTO_VERSION 12 /** * Minimum supported protocol version diff --git a/node/Peer.cpp b/node/Peer.cpp index b2b04c17f..08b792bb3 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -62,12 +62,12 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident throw ZT_EXCEPTION_INVALID_ARGUMENT; } - uint8_t ktmp[32]; + uint8_t ktmp[48]; KBKDFHMACSHA384(_key,ZT_KBKDF_LABEL_AES_GMAC_SIV_K0,0,0,ktmp); _aesKeys[0].init(ktmp); KBKDFHMACSHA384(_key,ZT_KBKDF_LABEL_AES_GMAC_SIV_K1,0,0,ktmp); _aesKeys[0].init(ktmp); - Utils::burn(ktmp, 32); + Utils::burn(ktmp, 48); } void Peer::received( diff --git a/node/Peer.hpp b/node/Peer.hpp index 63cbdbedc..cb7d8f314 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -533,11 +533,11 @@ public: */ inline int8_t bondingPolicy() { return _bondingPolicy; } - //const AES *aesKeysIfSupported() const - //{ return (const AES *)0; } - const AES *aesKeysIfSupported() const - { return (_vProto >= 10) ? _aesKeys : (const AES *)0; } + { return (const AES *)0; } + + //const AES *aesKeysIfSupported() const + //{ return (_vProto >= 12) ? _aesKeys : (const AES *)0; } private: struct _PeerPath diff --git a/node/Topology.cpp b/node/Topology.cpp index 9933f35a2..c50fc060f 100644 --- a/node/Topology.cpp +++ b/node/Topology.cpp @@ -363,13 +363,15 @@ void Topology::_memoizeUpstreams(void *tPtr) _amUpstream = false; for(std::vector::const_iterator i(_planet.roots().begin());i!=_planet.roots().end();++i) { - if (i->identity == RR->identity) { + const Identity &id = i->identity; + if (id == RR->identity) { _amUpstream = true; - } else if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),i->identity.address()) == _upstreamAddresses.end()) { - _upstreamAddresses.push_back(i->identity.address()); - SharedPtr &hp = _peers[i->identity.address()]; - if (!hp) - hp = new Peer(RR,RR->identity,i->identity); + } else if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),id.address()) == _upstreamAddresses.end()) { + _upstreamAddresses.push_back(id.address()); + SharedPtr &hp = _peers[id.address()]; + if (!hp) { + hp = new Peer(RR,RR->identity,id); + } } } diff --git a/node/Utils.hpp b/node/Utils.hpp index 26b848dcf..ec898fc0a 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -418,6 +418,53 @@ public: return true; } + /** + * Unconditionally swap bytes regardless of host byte order + * + * @param n Integer to swap + * @return Integer with bytes reversed + */ + static ZT_INLINE uint64_t swapBytes(const uint64_t n) noexcept + { + #ifdef __GNUC__ + return __builtin_bswap64(n); + #else + #ifdef _MSC_VER + return (uint64_t)_byteswap_uint64((unsigned __int64)n); + #else + return ( + ((n & 0x00000000000000ffULL) << 56) | + ((n & 0x000000000000ff00ULL) << 40) | + ((n & 0x0000000000ff0000ULL) << 24) | + ((n & 0x00000000ff000000ULL) << 8) | + ((n & 0x000000ff00000000ULL) >> 8) | + ((n & 0x0000ff0000000000ULL) >> 24) | + ((n & 0x00ff000000000000ULL) >> 40) | + ((n & 0xff00000000000000ULL) >> 56) + ); + #endif + #endif + } + + /** + * Unconditionally swap bytes regardless of host byte order + * + * @param n Integer to swap + * @return Integer with bytes reversed + */ + static ZT_INLINE uint32_t swapBytes(const uint32_t n) noexcept + { + #if defined(__GNUC__) + return __builtin_bswap32(n); + #else + #ifdef _MSC_VER + return (uint32_t)_byteswap_ulong((unsigned long)n); + #else + return htonl(n); + #endif + #endif + } + /** * Unconditionally swap bytes regardless of host byte order * diff --git a/version.h b/version.h index ccfe7d2a1..9fcd25bdb 100644 --- a/version.h +++ b/version.h @@ -27,7 +27,7 @@ /** * Revision */ -#define ZEROTIER_ONE_VERSION_REVISION 6 +#define ZEROTIER_ONE_VERSION_REVISION 8 /** * Build version From 3db263284bcd416b4499dc7d90e8403de8d4b1c2 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 10 Sep 2020 13:18:25 -0700 Subject: [PATCH 144/362] not sure how this got reverted --- controller/PostgreSQL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index b0709ad1c..a031c1ff0 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -1765,7 +1765,7 @@ void PostgreSQL::onlineNotification_Postgres() PQclear(res); } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::seconds(10)); } fprintf(stderr, "%s: Fell out of run loop in onlineNotificationThread\n", _myAddressStr.c_str()); PQfinish(conn); From bbb307aff740531dfe5a52ea56ab8ebaa86fe2ff Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 10 Sep 2020 15:59:18 -0700 Subject: [PATCH 145/362] DNS is now toggleable via `zerotier-cli set allowDNS=[0|1]` Flag is disabled by default as it should be opt-in on each endpoint --- one.cpp | 2 +- service/OneService.cpp | 35 ++++++++++++++++++++++++++++------- service/OneService.hpp | 5 +++++ service/README.md | 1 + 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/one.cpp b/one.cpp index 8b5ed4cf7..91d3e471a 100644 --- a/one.cpp +++ b/one.cpp @@ -734,7 +734,7 @@ static int cli(int argc,char **argv) } std::size_t eqidx = arg2.find('='); if (eqidx != std::string::npos) { - if ((arg2.substr(0,eqidx) == "allowManaged")||(arg2.substr(0,eqidx) == "allowGlobal")||(arg2.substr(0,eqidx) == "allowDefault")) { + if ((arg2.substr(0,eqidx) == "allowManaged")||(arg2.substr(0,eqidx) == "allowGlobal")||(arg2.substr(0,eqidx) == "allowDefault")||(arg2.substr(0,eqidx) == "allowDNS")) { char jsons[1024]; OSUtils::ztsnprintf(jsons,sizeof(jsons),"{\"%s\":%s}", arg2.substr(0,eqidx).c_str(), diff --git a/service/OneService.cpp b/service/OneService.cpp index 8071e5c77..c37f09001 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -72,6 +72,12 @@ #include #endif +#ifdef __APPLE__ +#include "../osdep/MacDNSHelper.hpp" +#elif defined(__WINDOWS__) +#include "../osdep/WinDNSHelper.hpp" +#endif + #ifdef ZT_USE_SYSTEM_HTTP_PARSER #include #else @@ -203,6 +209,7 @@ static void _networkToJson(nlohmann::json &nj,const ZT_VirtualNetworkConfig *nc, nj["allowManaged"] = localSettings.allowManaged; nj["allowGlobal"] = localSettings.allowGlobal; nj["allowDefault"] = localSettings.allowDefault; + nj["allowDNS"] = localSettings.allowDNS; nlohmann::json aa = nlohmann::json::array(); for(unsigned int i=0;iassignedAddressCount;++i) { @@ -515,6 +522,7 @@ public: settings.allowManaged = true; settings.allowGlobal = false; settings.allowDefault = false; + settings.allowDNS = false; memset(&config, 0, sizeof(ZT_VirtualNetworkConfig)); } @@ -1128,6 +1136,7 @@ public: fprintf(out,"allowManaged=%d\n",(int)n->second.settings.allowManaged); fprintf(out,"allowGlobal=%d\n",(int)n->second.settings.allowGlobal); fprintf(out,"allowDefault=%d\n",(int)n->second.settings.allowDefault); + fprintf(out,"allowDNS=%d\n",(int)n->second.settings.allowDNS); fclose(out); } @@ -1465,6 +1474,8 @@ public: if (allowGlobal.is_boolean()) localSettings.allowGlobal = (bool)allowGlobal; json &allowDefault = j["allowDefault"]; if (allowDefault.is_boolean()) localSettings.allowDefault = (bool)allowDefault; + json &allowDNS = j["allowDNS"]; + if (allowDNS.is_boolean()) localSettings.allowDNS = (bool)allowDNS; } } catch ( ... ) { // discard invalid JSON @@ -2006,16 +2017,25 @@ public: } if (syncDns) { - if (strlen(n.config.dns.domain) != 0) { - std::vector servers; - for (int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { - InetAddress a(n.config.dns.server_addr[j]); - if (a.isV4() || a.isV6()) { - servers.push_back(a); + if (n.settings.allowDNS) { + if (strlen(n.config.dns.domain) != 0) { + std::vector servers; + for (int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { + InetAddress a(n.config.dns.server_addr[j]); + if (a.isV4() || a.isV6()) { + servers.push_back(a); + } } + n.tap->setDns(n.config.dns.domain, servers); } - n.tap->setDns(n.config.dns.domain, servers); + } else { +#ifdef __APPLE__ + MacDNSHelper::removeDNS(n.config.nwid); +#elif defined(__WINDOWS__) + WinDNSHelper::removeDNS(n.config.nwid); +#endif } + } } @@ -2334,6 +2354,7 @@ public: } n.settings.allowGlobal = nc.getB("allowGlobal", false); n.settings.allowDefault = nc.getB("allowDefault", false); + n.settings.allowDNS = nc.getB("allowDNS", false); } } catch (std::exception &exc) { #ifdef __WINDOWS__ diff --git a/service/OneService.hpp b/service/OneService.hpp index 0b10770a5..b9c2cee55 100644 --- a/service/OneService.hpp +++ b/service/OneService.hpp @@ -86,6 +86,11 @@ public: * Allow overriding of system default routes for "full tunnel" operation? */ bool allowDefault; + + /** + * Allow configuration of DNS for the network + */ + bool allowDNS; }; /** diff --git a/service/README.md b/service/README.md index c77ee5110..4e9f98386 100644 --- a/service/README.md +++ b/service/README.md @@ -138,6 +138,7 @@ Most network settings are not writable, as they are defined by the network contr | allowManaged | boolean | Allow IP and route management | yes | | allowGlobal | boolean | Allow IPs and routes that overlap with global IPs | yes | | allowDefault | boolean | Allow overriding of system default route | yes | +| allowDNS | boolean | Allow configuration of DNS on network | yes | Route objects: From cb8d773634f9a79431e9ecdffa5623fc66d91a30 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 11 Sep 2020 13:36:21 -0400 Subject: [PATCH 146/362] Disable unicast compression as it almost never helps and usually just wastes CPU. --- node/Switch.cpp | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/node/Switch.cpp b/node/Switch.cpp index 1e7e2ca23..b2040455b 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -507,7 +507,16 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const network->pushCredentialsIfNeeded(tPtr,toZT,RR->node->now()); - if (fromBridged) { + if (!fromBridged) { + Packet outp(toZT,RR->identity.address(),Packet::VERB_FRAME); + outp.append(network->id()); + outp.append((uint16_t)etherType); + outp.append(data,len); + // 1.4.8: disable compression for unicast as it almost never helps + //if (!network->config().disableCompression()) + // outp.compress(); + aqm_enqueue(tPtr,network,outp,true,qosBucket,flowId); + } else { Packet outp(toZT,RR->identity.address(),Packet::VERB_EXT_FRAME); outp.append(network->id()); outp.append((unsigned char)0x00); @@ -515,16 +524,9 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const from.appendTo(outp); outp.append((uint16_t)etherType); outp.append(data,len); - if (!network->config().disableCompression()) - outp.compress(); - aqm_enqueue(tPtr,network,outp,true,qosBucket,flowId); - } else { - Packet outp(toZT,RR->identity.address(),Packet::VERB_FRAME); - outp.append(network->id()); - outp.append((uint16_t)etherType); - outp.append(data,len); - if (!network->config().disableCompression()) - outp.compress(); + // 1.4.8: disable compression for unicast as it almost never helps + //if (!network->config().disableCompression()) + // outp.compress(); aqm_enqueue(tPtr,network,outp,true,qosBucket,flowId); } } else { @@ -579,8 +581,9 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const from.appendTo(outp); outp.append((uint16_t)etherType); outp.append(data,len); - if (!network->config().disableCompression()) - outp.compress(); + // 1.4.8: disable compression for unicast as it almost never helps + //if (!network->config().disableCompression()) + // outp.compress(); aqm_enqueue(tPtr,network,outp,true,qosBucket,flowId); } else { RR->t->outgoingNetworkFrameDropped(tPtr,network,from,to,etherType,vlanId,len,"filter blocked (bridge replication)"); From 0e8b54f7a1ff2b064c5384260121074d2ac1731d Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Fri, 11 Sep 2020 14:47:18 -0700 Subject: [PATCH 147/362] Add minor trace output formatting changes. Change ZT_MULTIPATH_BOND_STATUS_INTERVAL from 30000 to 60000 --- node/Bond.cpp | 11 ++++++----- node/Constants.hpp | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/node/Bond.cpp b/node/Bond.cpp index 3f65c0fe4..aa8a5a426 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -362,7 +362,8 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR) { idx = abs((int)(flow->id() % (_numBondedPaths))); SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, _paths[_bondedIdx[idx]]->localSocket()); - sprintf(traceMsg, "%s (balance-xor) Assigned outgoing flow %x to peer %llx to link %s/%s, %lu active flow(s)\n", + _paths[_bondedIdx[idx]]->address().toString(curPathStr); + sprintf(traceMsg, "%s (balance-xor) Assigned outgoing flow %x to peer %llx to link %s/%s, %lu active flow(s)", OSUtils::humanReadableTimestamp().c_str(), flow->id(), _peer->_id.address().toInt(), link->ifname().c_str(), curPathStr, _flows.size()); RR->t->bondStateMessage(NULL, traceMsg); flow->assignPath(_paths[_bondedIdx[idx]],now); @@ -417,7 +418,7 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) } flow->assignedPath()->address().toString(curPathStr); SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, flow->assignedPath()->localSocket()); - sprintf(traceMsg, "%s (bond) Assigned outgoing flow %x to peer %llx to link %s/%s, %lu active flow(s)\n", + sprintf(traceMsg, "%s (bond) Assigned outgoing flow %x to peer %llx to link %s/%s, %lu active flow(s)", OSUtils::humanReadableTimestamp().c_str(), flow->id(), _peer->_id.address().toInt(), link->ifname().c_str(), curPathStr, _flows.size()); RR->t->bondStateMessage(NULL, traceMsg); return true; @@ -452,7 +453,7 @@ SharedPtr Bond::createFlow(const SharedPtr &path, int32_t flowId, un path->address().toString(curPathStr); path->_assignedFlowCount++; SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, flow->assignedPath()->localSocket()); - sprintf(traceMsg, "%s (bond) Assigned incoming flow %x from peer %llx to link %s/%s, %lu active flow(s)\n", + sprintf(traceMsg, "%s (bond) Assigned incoming flow %x from peer %llx to link %s/%s, %lu active flow(s)", OSUtils::humanReadableTimestamp().c_str(), flow->id(), _peer->_id.address().toInt(), link->ifname().c_str(), curPathStr, _flows.size()); RR->t->bondStateMessage(NULL, traceMsg); } @@ -475,7 +476,7 @@ void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) if (age) { // Remove by specific age while (it != _flows.end()) { if (it->second->age(now) > age) { - sprintf(traceMsg, "%s (bond) Forgetting flow %x between this node and peer %llx, %lu active flow(s)\n", + sprintf(traceMsg, "%s (bond) Forgetting flow %x between this node and peer %llx, %lu active flow(s)", OSUtils::humanReadableTimestamp().c_str(), it->first, _peer->_id.address().toInt(), (_flows.size()-1)); RR->t->bondStateMessage(NULL, traceMsg); it->second->assignedPath()->_assignedFlowCount--; @@ -495,7 +496,7 @@ void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) ++it; } if (oldestFlow != _flows.end()) { - sprintf(traceMsg, "%s (bond) Forgetting oldest flow %x (of age %llu) between this node and peer %llx, %lu active flow(s)\n", + sprintf(traceMsg, "%s (bond) Forgetting oldest flow %x (of age %llu) between this node and peer %llx, %lu active flow(s)", OSUtils::humanReadableTimestamp().c_str(), oldestFlow->first, oldestFlow->second->age(now), _peer->_id.address().toInt(), (_flows.size()-1)); RR->t->bondStateMessage(NULL, traceMsg); oldestFlow->second->assignedPath()->_assignedFlowCount--; diff --git a/node/Constants.hpp b/node/Constants.hpp index 5d6e735c4..d8c0a0d5f 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -396,7 +396,7 @@ /** * How often we emit a one-liner bond summary for each peer */ -#define ZT_MULTIPATH_BOND_STATUS_INTERVAL 30000 +#define ZT_MULTIPATH_BOND_STATUS_INTERVAL 60000 /** * How long before we consider a path to be dead in the general sense. This is From 62f23e0cfd5b97dbaeccdd892a1babed7d7942c4 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Fri, 11 Sep 2020 15:31:56 -0700 Subject: [PATCH 148/362] step 1 of `zerotier-cli dump` dump status, networks, peers, bonds & version --- one.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/one.cpp b/one.cpp index 91d3e471a..4b05ef58d 100644 --- a/one.cpp +++ b/one.cpp @@ -858,6 +858,58 @@ static int cli(int argc,char **argv) printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); return 1; } + } else if (command == "dump") { + std::stringstream dump; + + dump << "zerotier version: " << ZEROTIER_ONE_VERSION_MAJOR << "." + << ZEROTIER_ONE_VERSION_MINOR << "." << ZEROTIER_ONE_VERSION_REVISION << ZT_EOL_S << ZT_EOL_S; + + // grab status + dump << "status" << ZT_EOL_S << "------" << ZT_EOL_S; + unsigned int scode = Http::GET(1024 * 1024 * 16,60000,(const struct sockaddr *)&addr,"/status",requestHeaders,responseHeaders,responseBody); + if (scode != 200) { + printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str()); + return 1; + } + dump << responseBody << ZT_EOL_S; + + responseHeaders.clear(); + responseBody = ""; + + // grab network list + dump << ZT_EOL_S << "networks" << ZT_EOL_S << "--------" << ZT_EOL_S; + scode = Http::GET(1024 * 1024 * 16,60000,(const struct sockaddr *)&addr,"/network",requestHeaders,responseHeaders,responseBody); + if (scode != 200) { + printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str()); + return 1; + } + dump << responseBody << ZT_EOL_S; + + responseHeaders.clear(); + responseBody = ""; + + // list peers + dump << ZT_EOL_S << "peers" << ZT_EOL_S << "-----" << ZT_EOL_S; + scode = Http::GET(1024 * 1024 * 16,60000,(const struct sockaddr *)&addr,"/peer",requestHeaders,responseHeaders,responseBody); + if (scode != 200) { + printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str()); + return 1; + } + dump << responseBody << ZT_EOL_S; + + // get bonds + dump << ZT_EOL_S << "bonds" << ZT_EOL_S << "-----" << ZT_EOL_S; + scode = Http::GET(1024 * 1024 * 16,60000,(const struct sockaddr *)&addr,"/bonds",requestHeaders,responseHeaders,responseBody); + if (scode != 200) { + printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str()); + return 1; + } + dump << responseBody << ZT_EOL_S; + + responseHeaders.clear(); + responseBody = ""; + + fprintf(stderr, "%s", dump.str().c_str()); } else { cliPrintHelp(argv[0],stderr); return 0; From cf47618ffba08771170b03f46fb38cb3253cffbb Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Fri, 11 Sep 2020 16:09:46 -0700 Subject: [PATCH 149/362] Change ZT_MULTIPATH_FLOW_EXPIRATION_INTERVAL from 30 seconds to 5 minutes --- node/Constants.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/Constants.hpp b/node/Constants.hpp index d8c0a0d5f..562ae7be4 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -414,7 +414,7 @@ * How long before we consider a flow to be dead and remove it from the * policy's list. */ -#define ZT_MULTIPATH_FLOW_EXPIRATION_INTERVAL 30000 +#define ZT_MULTIPATH_FLOW_EXPIRATION_INTERVAL (60000 * 5) /** * How often a flow's statistical counters are reset From 36d867c3faf6604b027a9c35e69c2352c81beb0c Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Fri, 11 Sep 2020 16:43:30 -0700 Subject: [PATCH 150/362] more config dump for macOS --- one.cpp | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/one.cpp b/one.cpp index 4b05ef58d..6ce37f9a1 100644 --- a/one.cpp +++ b/one.cpp @@ -78,6 +78,13 @@ #include "ext/json/json.hpp" +#ifdef __APPLE__ +#include +#include +#include +#include +#endif + #define ZT_PID_PATH "zerotier-one.pid" using namespace ZeroTier; @@ -860,10 +867,17 @@ static int cli(int argc,char **argv) } } else if (command == "dump") { std::stringstream dump; - + dump << "platform: "; +#ifdef __APPLE__ + dump << "macOS" << ZT_EOL_S; +#elif defined(_WIN32) + dump << "Windows" << ZT_EOL_S; +#else + dump << "other unix based OS" << ZT_EOL_S; +#endif dump << "zerotier version: " << ZEROTIER_ONE_VERSION_MAJOR << "." << ZEROTIER_ONE_VERSION_MINOR << "." << ZEROTIER_ONE_VERSION_REVISION << ZT_EOL_S << ZT_EOL_S; - + // grab status dump << "status" << ZT_EOL_S << "------" << ZT_EOL_S; unsigned int scode = Http::GET(1024 * 1024 * 16,60000,(const struct sockaddr *)&addr,"/status",requestHeaders,responseHeaders,responseBody); @@ -909,7 +923,59 @@ static int cli(int argc,char **argv) responseHeaders.clear(); responseBody = ""; - fprintf(stderr, "%s", dump.str().c_str()); + dump << ZT_EOL_S << "local.conf" << ZT_EOL_S << "----------" << ZT_EOL_S; + // TODO: Dump local.conf + dump << "TODO" << ZT_EOL_S; + + dump << ZT_EOL_S << "Network Interfaces" << ZT_EOL_S << "------------------" << ZT_EOL_S << ZT_EOL_S; +#ifdef __APPLE__ + CFArrayRef interfaces = SCNetworkInterfaceCopyAll(); + CFIndex size = CFArrayGetCount(interfaces); + for(CFIndex i = 0; i < size; ++i) { + SCNetworkInterfaceRef iface = (SCNetworkInterfaceRef)CFArrayGetValueAtIndex(interfaces, i); + + dump << "Interface " << i << ZT_EOL_S << "-----------" << ZT_EOL_S; + CFStringRef tmp = SCNetworkInterfaceGetBSDName(iface); + char stringBuffer[512] = {}; + CFStringGetCString(tmp,stringBuffer, sizeof(stringBuffer), kCFStringEncodingUTF8); + dump << "Name: " << stringBuffer << ZT_EOL_S; + std::string ifName(stringBuffer); + int mtuCur, mtuMin, mtuMax; + SCNetworkInterfaceCopyMTU(iface, &mtuCur, &mtuMin, &mtuMax); + dump << "MTU: " << mtuCur << ZT_EOL_S; + tmp = SCNetworkInterfaceGetHardwareAddressString(iface); + CFStringGetCString(tmp, stringBuffer, sizeof(stringBuffer), kCFStringEncodingUTF8); + dump << "MAC: " << stringBuffer << ZT_EOL_S; + tmp = SCNetworkInterfaceGetInterfaceType(iface); + CFStringGetCString(tmp, stringBuffer, sizeof(stringBuffer), kCFStringEncodingUTF8); + dump << "Type: " << stringBuffer << ZT_EOL_S; + dump << "Addresses:" << ZT_EOL_S; + + struct ifaddrs *ifap, *ifa; + void *addr; + getifaddrs(&ifap); + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (strcmp(ifName.c_str(), ifa->ifa_name) == 0) { + if (ifa->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in *ipv4 = (struct sockaddr_in*)ifa->ifa_addr; + addr = &ipv4->sin_addr; + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6*)ifa->ifa_addr; + addr = &ipv6->sin6_addr; + } else { + continue; + } + inet_ntop(ifa->ifa_addr->sa_family, addr, stringBuffer, sizeof(stringBuffer)); + dump << stringBuffer << ZT_EOL_S; + } + } + + dump << ZT_EOL_S; + } +#endif + + fprintf(stderr, "%s", dump.str().c_str()); + } else { cliPrintHelp(argv[0],stderr); return 0; From be560eb704461a395794b219811ca214d0ca77f5 Mon Sep 17 00:00:00 2001 From: joseph-henry Date: Sat, 12 Sep 2020 12:23:49 -0700 Subject: [PATCH 151/362] Game Connection Issue Game connection issues can be resolved on our forums: discuss.zerotier.com --- .github/ISSUE_TEMPLATE/game_connection_issue.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/game_connection_issue.md diff --git a/.github/ISSUE_TEMPLATE/game_connection_issue.md b/.github/ISSUE_TEMPLATE/game_connection_issue.md new file mode 100644 index 000000000..0446b45c4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/game_connection_issue.md @@ -0,0 +1,6 @@ +Are you having trouble connecting to a game on your virtual network after installing ZeroTier? + +- [ ] Yes +- [ ] No + +If you answered yes it's very likely that your question would be better answered on our [forums](https://discuss.zerotier.com) instead of creating a GitHub issue. Ideally, Github tickets should be reserved for suspected bugs and and feature requests. From 94669a47090c5a127cb4f1476afc7d61894ebb7e Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Sat, 12 Sep 2020 12:58:10 -0700 Subject: [PATCH 152/362] Update Game issue template --- .github/ISSUE_TEMPLATE/game_connection_issue.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/game_connection_issue.md b/.github/ISSUE_TEMPLATE/game_connection_issue.md index 0446b45c4..0e1008f3e 100644 --- a/.github/ISSUE_TEMPLATE/game_connection_issue.md +++ b/.github/ISSUE_TEMPLATE/game_connection_issue.md @@ -1,3 +1,12 @@ +--- +name: Game Connection Issue +about: Game issues are better answered on our forum: https://discuss.zerotier.com +title: '' +labels: '' +assignees: '' + +--- + Are you having trouble connecting to a game on your virtual network after installing ZeroTier? - [ ] Yes From 2b9d9168e0303d008b4a496027c8bb21879a1e84 Mon Sep 17 00:00:00 2001 From: joseph-henry Date: Sat, 12 Sep 2020 13:08:31 -0700 Subject: [PATCH 153/362] Update issue templates --- .github/ISSUE_TEMPLATE/game-connection-issue.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/game-connection-issue.md diff --git a/.github/ISSUE_TEMPLATE/game-connection-issue.md b/.github/ISSUE_TEMPLATE/game-connection-issue.md new file mode 100644 index 000000000..e7e610aee --- /dev/null +++ b/.github/ISSUE_TEMPLATE/game-connection-issue.md @@ -0,0 +1,15 @@ +--- +name: Game Connection Issue +about: 'Game issues are better served by our forum: https://discuss.zerotier.com' +title: I'm probably submitting this issue in the wrong place +labels: '' +assignees: '' + +--- + +Are you having trouble connecting to a game on your virtual network after installing ZeroTier? + +- [ ] Yes +- [ ] No + +If you answered yes it's very likely that your question would be better answered on our [forums](https://discuss.zerotier.com) instead of creating a GitHub issue. Ideally, Github tickets should be reserved for suspected bugs and and feature requests. From 55f442f1a9911bc976c3f017b748487a0724142f Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Sat, 12 Sep 2020 13:09:51 -0700 Subject: [PATCH 154/362] Removed erroneous issue template --- .github/ISSUE_TEMPLATE/game_connection_issue.md | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/game_connection_issue.md diff --git a/.github/ISSUE_TEMPLATE/game_connection_issue.md b/.github/ISSUE_TEMPLATE/game_connection_issue.md deleted file mode 100644 index 0e1008f3e..000000000 --- a/.github/ISSUE_TEMPLATE/game_connection_issue.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -name: Game Connection Issue -about: Game issues are better answered on our forum: https://discuss.zerotier.com -title: '' -labels: '' -assignees: '' - ---- - -Are you having trouble connecting to a game on your virtual network after installing ZeroTier? - -- [ ] Yes -- [ ] No - -If you answered yes it's very likely that your question would be better answered on our [forums](https://discuss.zerotier.com) instead of creating a GitHub issue. Ideally, Github tickets should be reserved for suspected bugs and and feature requests. From 059dfee0c325c375d5748618d8e2bc5ad6b0ce8a Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 14 Sep 2020 12:42:39 -0700 Subject: [PATCH 155/362] fix windows compilation --- node/Utils.cpp | 1 + windows/ZeroTierOne/ZeroTierOne.vcxproj | 1 + windows/ZeroTierOne/ZeroTierOne.vcxproj.filters | 3 +++ 3 files changed, 5 insertions(+) diff --git a/node/Utils.cpp b/node/Utils.cpp index 7e3617c95..87a37b004 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -32,6 +32,7 @@ #ifdef __WINDOWS__ #include +#include #endif #include "Utils.hpp" diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj b/windows/ZeroTierOne/ZeroTierOne.vcxproj index 69036aa0a..75dcad6ce 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj @@ -50,6 +50,7 @@ + diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters index 6abe49c0d..f474c19b5 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters @@ -282,6 +282,9 @@ Source Files\osdep + + Source Files\node + From d980bba49f635bae8ebbec6a995f084a2a45a175 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 14 Sep 2020 12:42:39 -0700 Subject: [PATCH 156/362] fix windows compilation --- node/Utils.cpp | 1 + windows/ZeroTierOne/ZeroTierOne.vcxproj | 1 + windows/ZeroTierOne/ZeroTierOne.vcxproj.filters | 3 +++ 3 files changed, 5 insertions(+) diff --git a/node/Utils.cpp b/node/Utils.cpp index 7e3617c95..87a37b004 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -32,6 +32,7 @@ #ifdef __WINDOWS__ #include +#include #endif #include "Utils.hpp" diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj b/windows/ZeroTierOne/ZeroTierOne.vcxproj index 69036aa0a..75dcad6ce 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj @@ -50,6 +50,7 @@ + diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters index 6abe49c0d..f474c19b5 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters @@ -282,6 +282,9 @@ Source Files\osdep + + Source Files\node + From c210e9e5cf6219163010797bc24d632eb503d0e2 Mon Sep 17 00:00:00 2001 From: Michael Adams Date: Mon, 14 Sep 2020 12:58:29 -0700 Subject: [PATCH 157/362] Update issue templates Cleaned this up substantially --- .github/ISSUE_TEMPLATE/bugs-and-issues.md | 50 +++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bugs-and-issues.md diff --git a/.github/ISSUE_TEMPLATE/bugs-and-issues.md b/.github/ISSUE_TEMPLATE/bugs-and-issues.md new file mode 100644 index 000000000..8f1103b3c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bugs-and-issues.md @@ -0,0 +1,50 @@ +--- +name: Bugs and Issues +about: Create a report to help us improve +title: '' +labels: NEEDS TRIAGE +assignees: '' + +--- + +# Before filing a Bug Report + +_Using these will ensure you get quicker support, and make this space available for code-related issues. Thank you!_ + +- [Knowledge Base](https://zerotier.atlassian.net/wiki/spaces/SD/overview) => Guides and documentation on how to use ZeroTier. +- [Discuss Forum](https://discuss.zerotier.com/) => Our discussion forum for users and support to mutally resolve issues & suggest ideas. +- [Reddit](https://www.reddit.com/r/zerotier/) => Our subreddit, which we monitor regularly and is fairly active. +- https://www.zerotier.com/contact/ => Sales and licensing queries +- https://zerotier.atlassian.net/servicedesk/customer/portals => Customer Support Portal + +# If you still want to file a Bug Report + +## Required + +- What you expect to be happening. +- What is actually happening? +- Any steps to reproduce the error. +- Any screenshots that would help us out. + +## Additional information + +**Desktop (please complete the following information):** + - OS: [e.g. Mac, Linux, Windows, BSD] + - OS/Distribution Version + - ZeroTier Version [e.g. 1.4.6] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - ZeroTier Version [e.g. 1.4.6] + + **Embedded & NAS (please complete the following information):** + - Device: [e.g. Synology, Pi4] + - OS/Distribution (if applicable) + - ZeroTier Version [e.g. 1.4.6] + +**Additional context** +- ZeroTier Network Configuration: IPv4 & IPv6 networks defined on your ZeroTier Central +- Router Config: are you permitting port 9993, uPnP, and NAT-PMP? +- Firewall Config: are you permitting port 9993 on your OS; setting it to "Private" on Windows? +- Are you using this at home, in an office, college, etc? From 74d9e1e558fbff01d27fb21a2fc4c2fe6bff377c Mon Sep 17 00:00:00 2001 From: Michael Adams Date: Mon, 14 Sep 2020 12:59:36 -0700 Subject: [PATCH 158/362] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 49 ---------------------------- 1 file changed, 49 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 8da91fdf0..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Alternative, faster ways to get help** -If you have just started using ZeroTier, here are some places to get help: -- my.zerotier.com has a _Community_ tab. It's a live chat with other users and the developers. -- [ZeroTier Knowledge Base](https://zerotier.atlassian.net/wiki/spaces/SD/overview) -- www.zerotier.com has a Contact Us button -- email contact@zerotier.com - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Create a Network '...' -2. Install zerotier-one '....' -3. '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots or console output to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. Mac, Linux, Windows, BSD] - - OS/Distribution Version - - ZeroTier Version [e.g. 1.2.4] - - Hardware [e.g. raspberry pi 3] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Version [e.g. 1.2.4] - -**Additional context** -Add any other context about the problem here. -- ZeroTier Network Configuration -- Router Config -- Firewall Config (try turning the firewall off) -- General Network Environment: [ e.g Home, University Campus, Corporate LAN ] From 97801b3b566f7ab032f1291fc23e34f2e86295aa Mon Sep 17 00:00:00 2001 From: Michael Adams Date: Mon, 14 Sep 2020 13:10:41 -0700 Subject: [PATCH 159/362] Update issue templates --- .github/ISSUE_TEMPLATE/game-connection-issue.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/game-connection-issue.md b/.github/ISSUE_TEMPLATE/game-connection-issue.md index e7e610aee..0ce2a3202 100644 --- a/.github/ISSUE_TEMPLATE/game-connection-issue.md +++ b/.github/ISSUE_TEMPLATE/game-connection-issue.md @@ -1,8 +1,8 @@ --- name: Game Connection Issue -about: 'Game issues are better served by our forum: https://discuss.zerotier.com' -title: I'm probably submitting this issue in the wrong place -labels: '' +about: Game issues are better served by forum posts +title: Please go to our Discuss or Reddit for game-related issues. Thanks! +labels: wontfix assignees: '' --- @@ -12,4 +12,4 @@ Are you having trouble connecting to a game on your virtual network after instal - [ ] Yes - [ ] No -If you answered yes it's very likely that your question would be better answered on our [forums](https://discuss.zerotier.com) instead of creating a GitHub issue. Ideally, Github tickets should be reserved for suspected bugs and and feature requests. +If you answered yes, then it is very likely that your question would be better answered on our [Discuss ](https://discuss.zerotier.com) forum or [Reddit](https://www.reddit.com/r/zerotier/) community; we monitor both regularly. We also have extensive documentation on our [Knowledge Base](https://zerotier.atlassian.net/wiki/spaces/SD/overview). Thank you! From ace03d7c7bfeb5b00ef10ec5fdfc16f9bf3f04b6 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 14 Sep 2020 15:17:59 -0700 Subject: [PATCH 160/362] zerotier-cli dump for Windows --- one.cpp | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/one.cpp b/one.cpp index 6ce37f9a1..f582f94f9 100644 --- a/one.cpp +++ b/one.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include "osdep/WindowsEthernetTap.hpp" #include "windows/ZeroTierOne/ServiceInstaller.h" #include "windows/ZeroTierOne/ServiceBase.h" @@ -924,8 +926,14 @@ static int cli(int argc,char **argv) responseBody = ""; dump << ZT_EOL_S << "local.conf" << ZT_EOL_S << "----------" << ZT_EOL_S; - // TODO: Dump local.conf - dump << "TODO" << ZT_EOL_S; + std::string localConf; + OSUtils::readFile((homeDir + ZT_PATH_SEPARATOR_S + "local.conf").c_str(), localConf); + if (localConf.empty()) { + dump << "None Present" << ZT_EOL_S; + } + else { + dump << localConf << ZT_EOL_S; + } dump << ZT_EOL_S << "Network Interfaces" << ZT_EOL_S << "------------------" << ZT_EOL_S << ZT_EOL_S; #ifdef __APPLE__ @@ -972,9 +980,73 @@ static int cli(int argc,char **argv) dump << ZT_EOL_S; } +#elif defined(_WIN32) + ULONG buffLen = 16384; + PIP_ADAPTER_ADDRESSES addresses; + + ULONG ret = 0; + do { + addresses = (PIP_ADAPTER_ADDRESSES)malloc(buffLen); + + ret = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, addresses, &buffLen); + if (ret == ERROR_BUFFER_OVERFLOW) { + free(addresses); + addresses = NULL; + } + else { + break; + } + } while (ret == ERROR_BUFFER_OVERFLOW); + + int i = 0; + if (ret == NO_ERROR) { + PIP_ADAPTER_ADDRESSES curAddr = addresses; + while (curAddr) { + dump << "Interface " << i << ZT_EOL_S << "-----------" << ZT_EOL_S; + dump << "Name: " << curAddr->AdapterName << ZT_EOL_S; + dump << "MTU: " << curAddr->Mtu << ZT_EOL_S; + dump << "MAC: "; + char macBuffer[64] = {}; + sprintf(macBuffer, "%02x:%02x:%02x:%02x:%02x:%02x", + curAddr->PhysicalAddress[0], + curAddr->PhysicalAddress[1], + curAddr->PhysicalAddress[2], + curAddr->PhysicalAddress[3], + curAddr->PhysicalAddress[4], + curAddr->PhysicalAddress[5]); + dump << macBuffer << ZT_EOL_S; + dump << "Type: " << curAddr->IfType << ZT_EOL_S; + dump << "Addresses:" << ZT_EOL_S; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL; + pUnicast = curAddr->FirstUnicastAddress; + if (pUnicast) { + for (int j = 0; pUnicast != NULL; ++j) { + char buf[128] = {}; + DWORD bufLen = 128; + LPSOCKADDR a = pUnicast->Address.lpSockaddr; + WSAAddressToStringA( + pUnicast->Address.lpSockaddr, + pUnicast->Address.iSockaddrLength, + NULL, + buf, + &bufLen + ); + dump << buf << ZT_EOL_S; + pUnicast = pUnicast->Next; + } + } + + curAddr = curAddr->Next; + ++i; + } + } + if (addresses) { + free(addresses); + addresses = NULL; + } #endif - fprintf(stderr, "%s", dump.str().c_str()); + fprintf(stderr, "%s\n", dump.str().c_str()); } else { cliPrintHelp(argv[0],stderr); From f3c9ab8a7e907fa8d824bd65d24661782cca458d Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Fri, 11 Sep 2020 14:47:18 -0700 Subject: [PATCH 161/362] Add minor trace output formatting changes. Change ZT_MULTIPATH_BOND_STATUS_INTERVAL from 30000 to 60000 --- node/Bond.cpp | 11 ++++++----- node/Constants.hpp | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/node/Bond.cpp b/node/Bond.cpp index 3f65c0fe4..aa8a5a426 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -362,7 +362,8 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR) { idx = abs((int)(flow->id() % (_numBondedPaths))); SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, _paths[_bondedIdx[idx]]->localSocket()); - sprintf(traceMsg, "%s (balance-xor) Assigned outgoing flow %x to peer %llx to link %s/%s, %lu active flow(s)\n", + _paths[_bondedIdx[idx]]->address().toString(curPathStr); + sprintf(traceMsg, "%s (balance-xor) Assigned outgoing flow %x to peer %llx to link %s/%s, %lu active flow(s)", OSUtils::humanReadableTimestamp().c_str(), flow->id(), _peer->_id.address().toInt(), link->ifname().c_str(), curPathStr, _flows.size()); RR->t->bondStateMessage(NULL, traceMsg); flow->assignPath(_paths[_bondedIdx[idx]],now); @@ -417,7 +418,7 @@ bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) } flow->assignedPath()->address().toString(curPathStr); SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, flow->assignedPath()->localSocket()); - sprintf(traceMsg, "%s (bond) Assigned outgoing flow %x to peer %llx to link %s/%s, %lu active flow(s)\n", + sprintf(traceMsg, "%s (bond) Assigned outgoing flow %x to peer %llx to link %s/%s, %lu active flow(s)", OSUtils::humanReadableTimestamp().c_str(), flow->id(), _peer->_id.address().toInt(), link->ifname().c_str(), curPathStr, _flows.size()); RR->t->bondStateMessage(NULL, traceMsg); return true; @@ -452,7 +453,7 @@ SharedPtr Bond::createFlow(const SharedPtr &path, int32_t flowId, un path->address().toString(curPathStr); path->_assignedFlowCount++; SharedPtr link = RR->bc->getLinkBySocket(_policyAlias, flow->assignedPath()->localSocket()); - sprintf(traceMsg, "%s (bond) Assigned incoming flow %x from peer %llx to link %s/%s, %lu active flow(s)\n", + sprintf(traceMsg, "%s (bond) Assigned incoming flow %x from peer %llx to link %s/%s, %lu active flow(s)", OSUtils::humanReadableTimestamp().c_str(), flow->id(), _peer->_id.address().toInt(), link->ifname().c_str(), curPathStr, _flows.size()); RR->t->bondStateMessage(NULL, traceMsg); } @@ -475,7 +476,7 @@ void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) if (age) { // Remove by specific age while (it != _flows.end()) { if (it->second->age(now) > age) { - sprintf(traceMsg, "%s (bond) Forgetting flow %x between this node and peer %llx, %lu active flow(s)\n", + sprintf(traceMsg, "%s (bond) Forgetting flow %x between this node and peer %llx, %lu active flow(s)", OSUtils::humanReadableTimestamp().c_str(), it->first, _peer->_id.address().toInt(), (_flows.size()-1)); RR->t->bondStateMessage(NULL, traceMsg); it->second->assignedPath()->_assignedFlowCount--; @@ -495,7 +496,7 @@ void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) ++it; } if (oldestFlow != _flows.end()) { - sprintf(traceMsg, "%s (bond) Forgetting oldest flow %x (of age %llu) between this node and peer %llx, %lu active flow(s)\n", + sprintf(traceMsg, "%s (bond) Forgetting oldest flow %x (of age %llu) between this node and peer %llx, %lu active flow(s)", OSUtils::humanReadableTimestamp().c_str(), oldestFlow->first, oldestFlow->second->age(now), _peer->_id.address().toInt(), (_flows.size()-1)); RR->t->bondStateMessage(NULL, traceMsg); oldestFlow->second->assignedPath()->_assignedFlowCount--; diff --git a/node/Constants.hpp b/node/Constants.hpp index 5d6e735c4..d8c0a0d5f 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -396,7 +396,7 @@ /** * How often we emit a one-liner bond summary for each peer */ -#define ZT_MULTIPATH_BOND_STATUS_INTERVAL 30000 +#define ZT_MULTIPATH_BOND_STATUS_INTERVAL 60000 /** * How long before we consider a path to be dead in the general sense. This is From fa86b8bae0e37a39f094629fd30ff3c2b8270883 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Fri, 11 Sep 2020 16:09:46 -0700 Subject: [PATCH 162/362] Change ZT_MULTIPATH_FLOW_EXPIRATION_INTERVAL from 30 seconds to 5 minutes --- node/Constants.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/Constants.hpp b/node/Constants.hpp index d8c0a0d5f..562ae7be4 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -414,7 +414,7 @@ * How long before we consider a flow to be dead and remove it from the * policy's list. */ -#define ZT_MULTIPATH_FLOW_EXPIRATION_INTERVAL 30000 +#define ZT_MULTIPATH_FLOW_EXPIRATION_INTERVAL (60000 * 5) /** * How often a flow's statistical counters are reset From 7219ca0c0fe33c12afd5a1f49a8355741832f594 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Mon, 14 Sep 2020 20:44:21 -0400 Subject: [PATCH 163/362] AES works! Only with this or newer nodes. Uses salsa with older ones as usual. --- node/AES.hpp | 3 +-- node/Constants.hpp | 5 ----- node/Identity.hpp | 5 ++--- node/IncomingPacket.cpp | 4 ++-- node/Packet.cpp | 12 ++++++------ node/Peer.cpp | 9 ++++----- node/Peer.hpp | 10 +++++----- 7 files changed, 20 insertions(+), 28 deletions(-) diff --git a/node/AES.hpp b/node/AES.hpp index 5b639ce47..ba9e07497 100644 --- a/node/AES.hpp +++ b/node/AES.hpp @@ -376,9 +376,8 @@ public: */ ZT_INLINE void finish1() noexcept { - uint64_t tmp[2]; - // Compute 128-bit GMAC tag. + uint64_t tmp[2]; _gmac.finish(reinterpret_cast(tmp)); // Shorten to 64 bits, concatenate with message IV, and encrypt with AES to diff --git a/node/Constants.hpp b/node/Constants.hpp index 5d6e735c4..c2b302a82 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -201,11 +201,6 @@ */ #define ZT_TX_QUEUE_SIZE 32 -/** - * Length of secret key in bytes -- 256-bit -- do not change - */ -#define ZT_PEER_SECRET_KEY_LENGTH 32 - /** * Minimum delay between timer task checks to prevent thrashing */ diff --git a/node/Identity.hpp b/node/Identity.hpp index fc7ac55fa..e6f658dc3 100644 --- a/node/Identity.hpp +++ b/node/Identity.hpp @@ -173,13 +173,12 @@ public: * * @param id Identity to agree with * @param key Result parameter to fill with key bytes - * @param klen Length of key in bytes * @return Was agreement successful? */ - inline bool agree(const Identity &id,void *key,unsigned int klen) const + inline bool agree(const Identity &id,void *const key) const { if (_privateKey) { - C25519::agree(*_privateKey,id._publicKey,key,klen); + C25519::agree(*_privateKey,id._publicKey,key,ZT_SYMMETRIC_KEY_SIZE); return true; } return false; diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 56f1a5732..d64e7c1b0 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -286,8 +286,8 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool if (!RR->node->rateGateIdentityVerification(now,_path->address())) return true; - uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]; - if (RR->identity.agree(id,key,ZT_PEER_SECRET_KEY_LENGTH)) { + uint8_t key[ZT_SYMMETRIC_KEY_SIZE]; + if (RR->identity.agree(id,key)) { if (dearmor(key, peer->aesKeysIfSupported())) { // ensure packet is authentic, otherwise drop RR->t->incomingPacketDroppedHELLO(tPtr,_path,pid,fromAddress,"address collision"); Packet outp(id.address(),RR->identity.address(),Packet::VERB_ERROR); diff --git a/node/Packet.cpp b/node/Packet.cpp index 94168a503..1dbb4211b 100644 --- a/node/Packet.cpp +++ b/node/Packet.cpp @@ -881,7 +881,6 @@ void Packet::armor(const void *key,bool encryptPayload,const AES aesKeys[2]) uint8_t *const data = reinterpret_cast(unsafeData()); if ((aesKeys) && (encryptPayload)) { char tmp0[16],tmp1[16]; - printf("AES armor %.16llx %s -> %s %u\n",*reinterpret_cast(data),Address(data + ZT_PACKET_IDX_SOURCE,5).toString(tmp0),Address(data + ZT_PACKET_IDX_DEST,5).toString(tmp1),size()); setCipher(ZT_PROTO_CIPHER_SUITE__AES_GMAC_SIV); uint8_t *const payload = data + ZT_PACKET_IDX_VERB; @@ -899,7 +898,7 @@ void Packet::armor(const void *key,bool encryptPayload,const AES aesKeys[2]) Utils::copy<8>(data,tag); Utils::copy<8>(data + ZT_PACKET_IDX_MAC,tag + 1); #else - *reinterpret_cast(data) = tag[0]; + *reinterpret_cast(data + ZT_PACKET_IDX_IV) = tag[0]; *reinterpret_cast(data + ZT_PACKET_IDX_MAC) = tag[1]; #endif } else { @@ -947,20 +946,21 @@ bool Packet::dearmor(const void *key,const AES aesKeys[2]) if (cs == ZT_PROTO_CIPHER_SUITE__AES_GMAC_SIV) { if (aesKeys) { - printf("AES dearmor\n"); - AES::GMACSIVDecryptor dec(aesKeys[0],aesKeys[1]); - uint64_t tag[2]; #ifdef ZT_NO_UNALIGNED_ACCESS Utils::copy<8>(tag, data); Utils::copy<8>(tag + 1, data + ZT_PACKET_IDX_MAC); #else - tag[0] = *reinterpret_cast(data); + tag[0] = *reinterpret_cast(data + ZT_PACKET_IDX_IV); tag[1] = *reinterpret_cast(data + ZT_PACKET_IDX_MAC); #endif + AES::GMACSIVDecryptor dec(aesKeys[0],aesKeys[1]); dec.init(tag, payload); + const uint8_t oldFlags = data[ZT_PACKET_IDX_FLAGS]; + data[ZT_PACKET_IDX_FLAGS] &= 0xf8; dec.aad(data + ZT_PACKET_IDX_DEST,11); + data[ZT_PACKET_IDX_FLAGS] = oldFlags; dec.update(payload, payloadLen); return dec.finish(); } diff --git a/node/Peer.cpp b/node/Peer.cpp index 08b792bb3..3aa070e88 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -58,16 +58,15 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident _bondingPolicy(0), _lastComputedAggregateMeanLatency(0) { - if (!myIdentity.agree(peerIdentity,_key,ZT_PEER_SECRET_KEY_LENGTH)) { + if (!myIdentity.agree(peerIdentity,_key)) throw ZT_EXCEPTION_INVALID_ARGUMENT; - } - uint8_t ktmp[48]; + uint8_t ktmp[ZT_SYMMETRIC_KEY_SIZE]; KBKDFHMACSHA384(_key,ZT_KBKDF_LABEL_AES_GMAC_SIV_K0,0,0,ktmp); _aesKeys[0].init(ktmp); KBKDFHMACSHA384(_key,ZT_KBKDF_LABEL_AES_GMAC_SIV_K1,0,0,ktmp); - _aesKeys[0].init(ktmp); - Utils::burn(ktmp, 48); + _aesKeys[1].init(ktmp); + Utils::burn(ktmp,ZT_SYMMETRIC_KEY_SIZE); } void Peer::received( diff --git a/node/Peer.hpp b/node/Peer.hpp index cb7d8f314..0ee138bb5 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -533,11 +533,11 @@ public: */ inline int8_t bondingPolicy() { return _bondingPolicy; } - const AES *aesKeysIfSupported() const - { return (const AES *)0; } - //const AES *aesKeysIfSupported() const - //{ return (_vProto >= 12) ? _aesKeys : (const AES *)0; } + //{ return (const AES *)0; } + + const AES *aesKeysIfSupported() const + { return (_vProto >= 12) ? _aesKeys : (const AES *)0; } private: struct _PeerPath @@ -548,7 +548,7 @@ private: long priority; // >= 1, higher is better }; - uint8_t _key[ZT_PEER_SECRET_KEY_LENGTH]; + uint8_t _key[ZT_SYMMETRIC_KEY_SIZE]; AES _aesKeys[2]; const RuntimeEnvironment *RR; From 361ca1e8b458e783d322f1904335cc2ef357d328 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 15 Sep 2020 16:49:19 -0700 Subject: [PATCH 164/362] add link to CoreServices framework --- make-mac.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make-mac.mk b/make-mac.mk index c608e037d..02765d24e 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -32,7 +32,7 @@ ifeq ($(ZT_CONTROLLER),1) INCLUDES+=-I/usr/local/opt/libpq/include -Iext/hiredis-0.14.1/include/ -Iext/redis-plus-plus-1.1.1/install/macos/include/sw/ endif -LIBS+=-framework SystemConfiguration -framework CoreFoundation +LIBS+=-framework CoreServices -framework SystemConfiguration -framework CoreFoundation # Official releases are signed with our Apple cert and apply software updates by default ifeq ($(ZT_OFFICIAL_RELEASE),1) From 927aeb15f6aee1b4cf1769d59fce6b3e762384f2 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 15 Sep 2020 16:49:49 -0700 Subject: [PATCH 165/362] macos output dump to file if possible if not, write to stdout --- one.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/one.cpp b/one.cpp index d8a53dcd4..112ae7a94 100644 --- a/one.cpp +++ b/one.cpp @@ -87,6 +87,7 @@ #ifdef __APPLE__ #include +#include #include #include #include @@ -879,6 +880,8 @@ static int cli(int argc,char **argv) dump << "macOS" << ZT_EOL_S; #elif defined(_WIN32) dump << "Windows" << ZT_EOL_S; +#elif defined(__LINUX__) + dump << "Linux" << ZT_EOL_S; #else dump << "other unix based OS" << ZT_EOL_S; #endif @@ -985,6 +988,30 @@ static int cli(int argc,char **argv) dump << ZT_EOL_S; } + + + FSRef fsref; + UInt8 path[PATH_MAX]; + if (FSFindFolder(kUserDomain, kDesktopFolderType, kDontCreateFolder, &fsref) == noErr && + FSRefMakePath(&fsref, path, sizeof(path)) == noErr) { + + } else if (getenv("SUDO_USER")) { + sprintf((char*)path, "/Users/%s/Desktop/", getenv("SUDO_USER")); + } else { + fprintf(stdout, "%s", dump.str().c_str()); + return 0; + } + + sprintf((char*)path, "%s%szerotier_dump.txt", (char*)path, ZT_PATH_SEPARATOR_S); + + fprintf(stdout, "Writing dump to: %s\n", path); + int fd = open((char*)path, O_CREAT|O_RDWR,0664); + if (fd == -1) { + fprintf(stderr, "Error creating file.\n"); + return 1; + } + write(fd, dump.str().c_str(), dump.str().size()); + close(fd); #elif defined(_WIN32) ULONG buffLen = 16384; PIP_ADAPTER_ADDRESSES addresses; @@ -1118,6 +1145,7 @@ static int cli(int argc,char **argv) int fd = open(cwd, O_CREAT|O_RDWR,0664); if (fd == -1) { fprintf(stderr, "Error creating file.\n"); + return 1; } write(fd, dump.str().c_str(), dump.str().size()); close(fd); From 5090e950038102e6e658e94a207099fe12034e78 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 15 Sep 2020 16:50:55 -0700 Subject: [PATCH 166/362] dump basics to stdout on other platforms --- one.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/one.cpp b/one.cpp index 112ae7a94..500553a8d 100644 --- a/one.cpp +++ b/one.cpp @@ -1149,6 +1149,8 @@ static int cli(int argc,char **argv) } write(fd, dump.str().c_str(), dump.str().size()); close(fd); +#else + fprintf(stderr, "%s", dump.str().c_str()); #endif // fprintf(stderr, "%s\n", dump.str().c_str()); From cff315298595f9ef3c3e5975a21e8c45090969c3 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 16 Sep 2020 10:03:03 -0700 Subject: [PATCH 167/362] windows create dump file on desktop --- one.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/one.cpp b/one.cpp index 500553a8d..9005596ff 100644 --- a/one.cpp +++ b/one.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "osdep/WindowsEthernetTap.hpp" #include "windows/ZeroTierOne/ServiceInstaller.h" #include "windows/ZeroTierOne/ServiceBase.h" @@ -1076,6 +1077,41 @@ static int cli(int argc,char **argv) free(addresses); addresses = NULL; } + + char path[MAX_PATH + 1] = {}; + if (SHGetFolderPathA(NULL, CSIDL_DESKTOP, NULL, 0, path) == S_OK) { + sprintf(path, "%s%szerotier_dump.txt", path, ZT_PATH_SEPARATOR_S); + fprintf(stdout, "Writing dump to: %s\n", path); + HANDLE file = CreateFileA( + path, + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL + ); + if (file == INVALID_HANDLE_VALUE) { + fprintf(stdout, "%s", dump.str().c_str()); + return 0; + } + + BOOL err = WriteFile( + file, + dump.str().c_str(), + dump.str().size(), + NULL, + NULL + ); + if (err = FALSE) { + fprintf(stderr, "Error writing file"); + return 1; + } + CloseHandle(file); + } + else { + fprintf(stdout, "%s", dump.str().c_str()); + } #elif defined(__LINUX__) struct ifreq ifr; struct ifconf ifc; From 1883a8c9ee802300c5d0fce5daa92e5bd08a4bee Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 16 Sep 2020 10:15:42 -0700 Subject: [PATCH 168/362] Set 198.18.0.0/15 to IP_SCOPE_PRIVATE --- node/InetAddress.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/node/InetAddress.cpp b/node/InetAddress.cpp index bca97ad2f..58fa0ba9d 100644 --- a/node/InetAddress.cpp +++ b/node/InetAddress.cpp @@ -60,6 +60,9 @@ InetAddress::IpScope InetAddress::ipScope() const case 0xc0: if ((ip & 0xffff0000) == 0xc0a80000) return IP_SCOPE_PRIVATE; // 192.168.0.0/16 break; + case 0xc6: + if ((ip & 0xfffe0000) == 0xc6120000) return IP_SCOPE_PRIVATE; // 198.18.0.0/15 + break; case 0xff: return IP_SCOPE_NONE; // 255.0.0.0/8 (broadcast, or unused/unusable) } switch(ip >> 28) { From 221e4ecb12e1dbef74aabcfb140faec8baf94e14 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 16 Sep 2020 10:24:36 -0700 Subject: [PATCH 169/362] Add "documentation" networks as IP_SCOPE_PRIVATE https://en.wikipedia.org/wiki/Reserved_IP_addresses --- node/InetAddress.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/node/InetAddress.cpp b/node/InetAddress.cpp index 58fa0ba9d..5c3d94ee2 100644 --- a/node/InetAddress.cpp +++ b/node/InetAddress.cpp @@ -59,10 +59,14 @@ InetAddress::IpScope InetAddress::ipScope() const break; case 0xc0: if ((ip & 0xffff0000) == 0xc0a80000) return IP_SCOPE_PRIVATE; // 192.168.0.0/16 + if ((ip & 0xffffff00) == 0xc0000200) return IP_SCOPE_PRIVATE; // 192.0.2.0/24 break; case 0xc6: - if ((ip & 0xfffe0000) == 0xc6120000) return IP_SCOPE_PRIVATE; // 198.18.0.0/15 + if ((ip & 0xfffe0000) == 0xc6120000) return IP_SCOPE_PRIVATE; // 198.18.0.0/15 + if ((ip & 0xffffff00) == 0xc6336400) return IP_SCOPE_PRIVATE; // 198.51.100.0/24 break; + case 0xcb: + if ((ip & 0xffffff00) == 0xcb007100) return IP_SCOPE_PRIVATE; // 203.0.113.0/24 case 0xff: return IP_SCOPE_NONE; // 255.0.0.0/8 (broadcast, or unused/unusable) } switch(ip >> 28) { From 4da9bed4fa5546feb28d21fe5d458d49fb7f5714 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 16 Sep 2020 10:52:23 -0700 Subject: [PATCH 170/362] add 'dump' to cli help --- one.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/one.cpp b/one.cpp index 9005596ff..00ca8e8c4 100644 --- a/one.cpp +++ b/one.cpp @@ -140,6 +140,7 @@ static void cliPrintHelp(const char *pn,FILE *out) fprintf(out," listmoons - List moons (federated root sets)" ZT_EOL_S); fprintf(out," orbit - Join a moon via any member root" ZT_EOL_S); fprintf(out," deorbit - Leave a moon" ZT_EOL_S); + fprintf(out," dump - Debug settings dump for support" ZT_EOL_S); fprintf(out,ZT_EOL_S"Available settings:" ZT_EOL_S); fprintf(out," Settings to use with [get/set] may include property names from " ZT_EOL_S); fprintf(out," the JSON output of \"zerotier-cli -j listnetworks\". Additionally, " ZT_EOL_S); From afcbc6dd9faa61924ff9d7449c50d3eaf3bfb03a Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 16 Sep 2020 10:54:14 -0700 Subject: [PATCH 171/362] clean up some error output --- one.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/one.cpp b/one.cpp index 00ca8e8c4..82a1cd651 100644 --- a/one.cpp +++ b/one.cpp @@ -250,11 +250,9 @@ static int cli(int argc,char **argv) homeDir = OneService::platformDefaultHomePath(); // TODO: cleanup this logic - // A lot of generic CLI errors land here; missing admin rights cause a bit of this. if ((!port)||(!authToken.length())) { if (!homeDir.length()) { fprintf(stderr,"%s: missing port or authentication token and no home directory specified to auto-detect" ZT_EOL_S,argv[0]); - fprintf(stderr, "If you did not, please run this command as an Administrator / sudo / Root user. Thanks!"); return 2; } @@ -264,7 +262,6 @@ static int cli(int argc,char **argv) port = Utils::strToUInt(portStr.c_str()); if ((port == 0)||(port > 0xffff)) { fprintf(stderr,"%s: missing port and zerotier-one.port not found in %s" ZT_EOL_S,argv[0],homeDir.c_str()); - fprintf(stderr, "If you did not, please run this command as an Administrator / sudo / Root user. Thanks!"); return 2; } } @@ -287,7 +284,6 @@ static int cli(int argc,char **argv) #endif if (!authToken.length()) { fprintf(stderr,"%s: missing authentication token and authtoken.secret not found (or readable) in %s" ZT_EOL_S,argv[0],homeDir.c_str()); - fprintf(stderr, "If you did not, please run this command as an Administrator / sudo / Root user. Thanks!"); return 2; } } From 04f6140da66e24d1a27d293f2d1f08d25f07192e Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 16 Sep 2020 22:47:13 +0000 Subject: [PATCH 172/362] AES builds and works now on ARM64. --- make-linux.mk | 4 ++-- node/Constants.hpp | 8 ++++++++ node/Utils.cpp | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/make-linux.mk b/make-linux.mk index 71ead3650..2cc6fcb36 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -208,11 +208,11 @@ ifeq ($(CC_MACH),armv7hl) endif ifeq ($(CC_MACH),arm64) ZT_ARCHITECTURE=4 - override DEFS+=-DZT_NO_TYPE_PUNNING + override DEFS+=-DZT_NO_TYPE_PUNNING -DZT_ARCH_ARM_HAS_NEON -march=armv8-a+aes+crypto -mtune=generic -mstrict-align endif ifeq ($(CC_MACH),aarch64) ZT_ARCHITECTURE=4 - override DEFS+=-DZT_NO_TYPE_PUNNING + override DEFS+=-DZT_NO_TYPE_PUNNING -DZT_ARCH_ARM_HAS_NEON -march=armv8-a+aes+crypto -mtune=generic -mstrict-align endif ifeq ($(CC_MACH),mipsel) ZT_ARCHITECTURE=5 diff --git a/node/Constants.hpp b/node/Constants.hpp index bcb529e90..53b44dd42 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -111,6 +111,14 @@ #include #endif +#if (defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(ZT_ARCH_ARM_HAS_NEON)) +#ifndef ZT_ARCH_ARM_HAS_NEON +#define ZT_ARCH_ARM_HAS_NEON 1 +#endif +#include +/*#include */ +#endif + // Define ZT_NO_TYPE_PUNNING to disable reckless casts on anything other than x86/x64. #if (!(defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_AMD64) || defined(_M_X64) || defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || defined(_X86_) || defined(__I86__) || defined(__INTEL__) || defined(__386))) #ifndef ZT_NO_TYPE_PUNNING diff --git a/node/Utils.cpp b/node/Utils.cpp index 87a37b004..508e553e3 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -28,6 +28,9 @@ #include #include #include +#ifdef __LINUX__ +#include +#endif #endif #ifdef __WINDOWS__ From e7dafb3ae66e8e121274de3f8e46eb882313b7b7 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Fri, 18 Sep 2020 09:30:43 -0700 Subject: [PATCH 173/362] allow DNS field for mac UI --- macui/ZeroTier One/AppDelegate.m | 1 + .../ZeroTier One/JoinNetworkViewController.h | 1 + .../ZeroTier One/JoinNetworkViewController.m | 2 + .../JoinNetworkViewController.xib | 87 ++++---- macui/ZeroTier One/Network.h | 1 + macui/ZeroTier One/Network.m | 15 ++ macui/ZeroTier One/NetworkInfoCell.h | 1 + macui/ZeroTier One/NetworkInfoCell.m | 1 + macui/ZeroTier One/ServiceCom.h | 2 +- macui/ZeroTier One/ServiceCom.m | 8 +- .../ZeroTier One/ShowNetworksViewController.m | 3 + .../ShowNetworksViewController.xib | 205 ++++++++++-------- 12 files changed, 195 insertions(+), 132 deletions(-) diff --git a/macui/ZeroTier One/AppDelegate.m b/macui/ZeroTier One/AppDelegate.m index ae3e042f1..6889a89aa 100644 --- a/macui/ZeroTier One/AppDelegate.m +++ b/macui/ZeroTier One/AppDelegate.m @@ -336,6 +336,7 @@ allowManaged:network.allowManaged allowGlobal:network.allowGlobal allowDefault:(network.allowDefault && ![Network defaultRouteExists:self.networks]) + allowDNS:network.allowDNS error:&error]; if (error) { diff --git a/macui/ZeroTier One/JoinNetworkViewController.h b/macui/ZeroTier One/JoinNetworkViewController.h index 428959fba..4c25cc0db 100644 --- a/macui/ZeroTier One/JoinNetworkViewController.h +++ b/macui/ZeroTier One/JoinNetworkViewController.h @@ -30,6 +30,7 @@ extern NSString * const JoinedNetworksKey; @property (nonatomic, weak) IBOutlet NSButton *allowManagedCheckBox; @property (nonatomic, weak) IBOutlet NSButton *allowGlobalCheckBox; @property (nonatomic, weak) IBOutlet NSButton *allowDefaultCheckBox; +@property (nonatomic, weak) IBOutlet NSButton *allowDNSCheckBox; @property (nonatomic, weak) IBOutlet AppDelegate *appDelegate; @property (nonatomic) NSMutableArray *values; diff --git a/macui/ZeroTier One/JoinNetworkViewController.m b/macui/ZeroTier One/JoinNetworkViewController.m index cae265416..c2d6517a1 100644 --- a/macui/ZeroTier One/JoinNetworkViewController.m +++ b/macui/ZeroTier One/JoinNetworkViewController.m @@ -54,6 +54,7 @@ NSString * const JoinedNetworksKey = @"com.zerotier.one.joined-networks"; self.allowManagedCheckBox.state = NSOnState; self.allowGlobalCheckBox.state = NSOffState; self.allowDefaultCheckBox.state = NSOffState; + self.allowDNSCheckBox.state = NSOffState; self.network.stringValue = @""; @@ -82,6 +83,7 @@ NSString * const JoinedNetworksKey = @"com.zerotier.one.joined-networks"; allowManaged:(self.allowManagedCheckBox.state == NSOnState) allowGlobal:(self.allowGlobalCheckBox.state == NSOnState) allowDefault:(self.allowDefaultCheckBox.state == NSOnState) + allowDNS:(self.allowDNSCheckBox.state == NSOnState) error:&error]; if(error) { diff --git a/macui/ZeroTier One/JoinNetworkViewController.xib b/macui/ZeroTier One/JoinNetworkViewController.xib index 59161093f..b4ea477bf 100644 --- a/macui/ZeroTier One/JoinNetworkViewController.xib +++ b/macui/ZeroTier One/JoinNetworkViewController.xib @@ -1,13 +1,14 @@ - + - + + @@ -19,11 +20,11 @@ - + - - + + @@ -31,6 +32,47 @@ + + + + + + + + + + + + + - - - - - - - - - - - - - + diff --git a/macui/ZeroTier One/Network.h b/macui/ZeroTier One/Network.h index 957ff8d56..c1cfbaf28 100644 --- a/macui/ZeroTier One/Network.h +++ b/macui/ZeroTier One/Network.h @@ -50,6 +50,7 @@ enum NetworkType { @property (readonly) BOOL allowManaged; @property (readonly) BOOL allowGlobal; @property (readonly) BOOL allowDefault; +@property (readonly) BOOL allowDNS; @property (readonly) BOOL connected; // not persisted. set to YES if loaded via json - (id)initWithJsonData:(NSDictionary*)jsonData; diff --git a/macui/ZeroTier One/Network.m b/macui/ZeroTier One/Network.m index 8474acaaf..2379eb691 100644 --- a/macui/ZeroTier One/Network.m +++ b/macui/ZeroTier One/Network.m @@ -35,6 +35,7 @@ NSString *NetworkTypeKey = @"type"; NSString *NetworkAllowManagedKey = @"allowManaged"; NSString *NetworkAllowGlobalKey = @"allowGlobal"; NSString *NetworkAllowDefaultKey = @"allowDefault"; +NSString *NetworkAllowDNSKey = @"allowDNS"; @implementation Network @@ -101,6 +102,11 @@ NSString *NetworkAllowDefaultKey = @"allowDefault"; if([jsonData objectForKey:@"allowDefault"]) { _allowDefault = [(NSNumber*)[jsonData objectForKey:@"allowDefault"] boolValue]; } + if([jsonData objectForKey:@"allowDNS"]) { + _allowDNS = [(NSNumber*)[jsonData objectForKey:@"allowDNS"] boolValue]; + } else { + _allowDNS = false; + } if([jsonData objectForKey:@"status"]) { NSString *statusStr = (NSString*)[jsonData objectForKey:@"status"]; @@ -207,6 +213,12 @@ NSString *NetworkAllowDefaultKey = @"allowDefault"; if([aDecoder containsValueForKey:NetworkAllowDefaultKey]) { _allowDefault = [aDecoder decodeBoolForKey:NetworkAllowDefaultKey]; } + + if([aDecoder containsValueForKey:NetworkAllowDNSKey]) { + _allowDNS = [aDecoder decodeBoolForKey:NetworkAllowDNSKey]; + } else { + _allowDNS = false; + } _connected = NO; } @@ -233,6 +245,7 @@ NSString *NetworkAllowDefaultKey = @"allowDefault"; [aCoder encodeBool:_allowManaged forKey:NetworkAllowManagedKey]; [aCoder encodeBool:_allowGlobal forKey:NetworkAllowGlobalKey]; [aCoder encodeBool:_allowDefault forKey:NetworkAllowDefaultKey]; + [aCoder encodeBool:_allowDNS forKey:NetworkAllowDNSKey]; } + (BOOL)defaultRouteExists:(NSArray*)netList @@ -297,6 +310,7 @@ NSString *NetworkAllowDefaultKey = @"allowDefault"; self.allowManaged == network.allowManaged && self.allowGlobal == network.allowGlobal && self.allowDefault == network.allowDefault && + self.allowDNS == network.allowDNS && self.connected == network.connected; } @@ -331,6 +345,7 @@ NSString *NetworkAllowDefaultKey = @"allowDefault"; self.allowManaged ^ self.allowGlobal ^ self.allowDefault ^ + self.allowDNS ^ self.connected; } diff --git a/macui/ZeroTier One/NetworkInfoCell.h b/macui/ZeroTier One/NetworkInfoCell.h index be9345d70..f764034ee 100644 --- a/macui/ZeroTier One/NetworkInfoCell.h +++ b/macui/ZeroTier One/NetworkInfoCell.h @@ -37,6 +37,7 @@ @property (weak, nonatomic) IBOutlet NSButton *allowManaged; @property (weak, nonatomic) IBOutlet NSButton *allowGlobal; @property (weak, nonatomic) IBOutlet NSButton *allowDefault; +@property (weak, nonatomic) IBOutlet NSButton *allowDNS; @property (weak, nonatomic) IBOutlet NSButton *connectedCheckbox; @property (weak, nonatomic) IBOutlet NSButton *deleteButton; diff --git a/macui/ZeroTier One/NetworkInfoCell.m b/macui/ZeroTier One/NetworkInfoCell.m index dc75cab39..df1bbf67e 100644 --- a/macui/ZeroTier One/NetworkInfoCell.m +++ b/macui/ZeroTier One/NetworkInfoCell.m @@ -57,6 +57,7 @@ allowManaged:(self.allowManaged.state == NSOnState) allowGlobal:(self.allowGlobal.state == NSOnState) allowDefault:![Network defaultRouteExists:_parent.networkList] && (self.allowDefault.state == NSOnState) + allowDNS:(self.allowDNS.state == NSOnState) error:&error]; if (error) { diff --git a/macui/ZeroTier One/ServiceCom.h b/macui/ZeroTier One/ServiceCom.h index c2b4692f8..17b738e4d 100644 --- a/macui/ZeroTier One/ServiceCom.h +++ b/macui/ZeroTier One/ServiceCom.h @@ -34,7 +34,7 @@ - (void)getNetworklist:(void (^)(NSArray*))completionHandler error:(NSError* __autoreleasing *)error; - (void)getNodeStatus:(void (^)(NodeStatus*))completionHandler error:(NSError*__autoreleasing*)error; -- (void)joinNetwork:(NSString*)networkId allowManaged:(BOOL)allowManaged allowGlobal:(BOOL)allowGlobal allowDefault:(BOOL)allowDefault error:(NSError*__autoreleasing*)error; +- (void)joinNetwork:(NSString*)networkId allowManaged:(BOOL)allowManaged allowGlobal:(BOOL)allowGlobal allowDefault:(BOOL)allowDefault allowDNS:(BOOL)allowDNS error:(NSError*__autoreleasing*)error; - (void)leaveNetwork:(NSString*)networkId error:(NSError*__autoreleasing*)error; @end diff --git a/macui/ZeroTier One/ServiceCom.m b/macui/ZeroTier One/ServiceCom.m index 75b98502f..55d67741a 100644 --- a/macui/ZeroTier One/ServiceCom.m +++ b/macui/ZeroTier One/ServiceCom.m @@ -377,7 +377,12 @@ [task resume]; } -- (void)joinNetwork:(NSString*)networkId allowManaged:(BOOL)allowManaged allowGlobal:(BOOL)allowGlobal allowDefault:(BOOL)allowDefault error:(NSError *__autoreleasing*)error +- (void)joinNetwork:(NSString*)networkId + allowManaged:(BOOL)allowManaged + allowGlobal:(BOOL)allowGlobal + allowDefault:(BOOL)allowDefault + allowDNS:(BOOL)allowDNS + error:(NSError *__autoreleasing*)error { NSString *key = [self key:error]; if(*error) { @@ -395,6 +400,7 @@ [jsonDict setObject:[NSNumber numberWithBool:allowManaged] forKey:@"allowManaged"]; [jsonDict setObject:[NSNumber numberWithBool:allowGlobal] forKey:@"allowGlobal"]; [jsonDict setObject:[NSNumber numberWithBool:allowDefault] forKey:@"allowDefault"]; + [jsonDict setObject:[NSNumber numberWithBool:allowDNS] forKey:@"allowDNS"]; NSError *err = nil; diff --git a/macui/ZeroTier One/ShowNetworksViewController.m b/macui/ZeroTier One/ShowNetworksViewController.m index 903a4b447..acd29479a 100644 --- a/macui/ZeroTier One/ShowNetworksViewController.m +++ b/macui/ZeroTier One/ShowNetworksViewController.m @@ -158,16 +158,19 @@ BOOL hasNetworkWithID(NSArray *list, UInt64 nwid) cell.allowGlobal.enabled = YES; cell.allowManaged.enabled = YES; + cell.allowDNS.enabled = YES; } else { cell.connectedCheckbox.state = NSOffState; cell.allowDefault.enabled = NO; cell.allowGlobal.enabled = NO; cell.allowManaged.enabled = NO; + cell.allowDNS.enabled = NO; } cell.allowGlobal.state = network.allowGlobal ? NSOnState : NSOffState; cell.allowManaged.state = network.allowManaged ? NSOnState : NSOffState; + cell.allowDNS.state = network.allowDNS ? NSOnState : NSOffState; cell.addressesField.stringValue = @""; diff --git a/macui/ZeroTier One/ShowNetworksViewController.xib b/macui/ZeroTier One/ShowNetworksViewController.xib index 62ac289a2..485adb0c0 100644 --- a/macui/ZeroTier One/ShowNetworksViewController.xib +++ b/macui/ZeroTier One/ShowNetworksViewController.xib @@ -1,8 +1,8 @@ - + - + @@ -18,15 +18,15 @@ - + - + - + @@ -34,7 +34,6 @@ - @@ -46,109 +45,83 @@ - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + - - - + + - - + + @@ -156,7 +129,7 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

)(vBBD6%xdR(Tm1Y4^@?@^H0 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�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 Date: Mon, 11 May 2020 15:24:13 -0700 Subject: [PATCH 050/362] redis++ binaries for mac --- ext/redis-plus-plus-1.1.1/.gitignore | 32 + ext/redis-plus-plus-1.1.1/CMakeLists.txt | 51 + ext/redis-plus-plus-1.1.1/LICENSE | 201 ++ ext/redis-plus-plus-1.1.1/README.md | 1776 +++++++++++++ .../macos/include/sw/redis++/command.h | 2233 +++++++++++++++++ .../macos/include/sw/redis++/command_args.h | 180 ++ .../include/sw/redis++/command_options.h | 211 ++ .../macos/include/sw/redis++/connection.h | 194 ++ .../include/sw/redis++/connection_pool.h | 115 + .../install/macos/include/sw/redis++/errors.h | 159 ++ .../macos/include/sw/redis++/pipeline.h | 49 + .../macos/include/sw/redis++/queued_redis.h | 1844 ++++++++++++++ .../macos/include/sw/redis++/queued_redis.hpp | 208 ++ .../macos/include/sw/redis++/redis++.h | 25 + .../install/macos/include/sw/redis++/redis.h | 1523 +++++++++++ .../macos/include/sw/redis++/redis.hpp | 1365 ++++++++++ .../macos/include/sw/redis++/redis_cluster.h | 1395 ++++++++++ .../include/sw/redis++/redis_cluster.hpp | 1415 +++++++++++ .../install/macos/include/sw/redis++/reply.h | 363 +++ .../macos/include/sw/redis++/sentinel.h | 138 + .../install/macos/include/sw/redis++/shards.h | 115 + .../macos/include/sw/redis++/shards_pool.h | 137 + .../macos/include/sw/redis++/subscriber.h | 231 ++ .../macos/include/sw/redis++/transaction.h | 77 + .../install/macos/include/sw/redis++/utils.h | 269 ++ .../src/sw/redis++/command.cpp | 376 +++ .../src/sw/redis++/command.h | 2233 +++++++++++++++++ .../src/sw/redis++/command_args.h | 180 ++ .../src/sw/redis++/command_options.cpp | 201 ++ .../src/sw/redis++/command_options.h | 211 ++ .../src/sw/redis++/connection.cpp | 305 +++ .../src/sw/redis++/connection.h | 194 ++ .../src/sw/redis++/connection_pool.cpp | 249 ++ .../src/sw/redis++/connection_pool.h | 115 + .../src/sw/redis++/crc16.cpp | 96 + .../src/sw/redis++/errors.cpp | 136 + .../src/sw/redis++/errors.h | 159 ++ .../src/sw/redis++/pipeline.cpp | 35 + .../src/sw/redis++/pipeline.h | 49 + .../src/sw/redis++/queued_redis.h | 1844 ++++++++++++++ .../src/sw/redis++/queued_redis.hpp | 208 ++ .../src/sw/redis++/redis++.h | 25 + .../src/sw/redis++/redis.cpp | 882 +++++++ .../src/sw/redis++/redis.h | 1523 +++++++++++ .../src/sw/redis++/redis.hpp | 1365 ++++++++++ .../src/sw/redis++/redis_cluster.cpp | 769 ++++++ .../src/sw/redis++/redis_cluster.h | 1395 ++++++++++ .../src/sw/redis++/redis_cluster.hpp | 1415 +++++++++++ .../src/sw/redis++/reply.cpp | 150 ++ .../src/sw/redis++/reply.h | 363 +++ .../src/sw/redis++/sentinel.cpp | 361 +++ .../src/sw/redis++/sentinel.h | 138 + .../src/sw/redis++/shards.cpp | 50 + .../src/sw/redis++/shards.h | 115 + .../src/sw/redis++/shards_pool.cpp | 319 +++ .../src/sw/redis++/shards_pool.h | 137 + .../src/sw/redis++/subscriber.cpp | 222 ++ .../src/sw/redis++/subscriber.h | 231 ++ .../src/sw/redis++/transaction.cpp | 123 + .../src/sw/redis++/transaction.h | 77 + .../src/sw/redis++/utils.h | 269 ++ ext/redis-plus-plus-1.1.1/test/CMakeLists.txt | 33 + .../test/src/sw/redis++/benchmark_test.h | 83 + .../test/src/sw/redis++/benchmark_test.hpp | 178 ++ .../src/sw/redis++/connection_cmds_test.h | 49 + .../src/sw/redis++/connection_cmds_test.hpp | 50 + .../test/src/sw/redis++/geo_cmds_test.h | 47 + .../test/src/sw/redis++/geo_cmds_test.hpp | 149 ++ .../test/src/sw/redis++/hash_cmds_test.h | 55 + .../test/src/sw/redis++/hash_cmds_test.hpp | 177 ++ .../src/sw/redis++/hyperloglog_cmds_test.h | 47 + .../src/sw/redis++/hyperloglog_cmds_test.hpp | 67 + .../test/src/sw/redis++/keys_cmds_test.h | 55 + .../test/src/sw/redis++/keys_cmds_test.hpp | 166 ++ .../test/src/sw/redis++/list_cmds_test.h | 55 + .../test/src/sw/redis++/list_cmds_test.hpp | 154 ++ .../sw/redis++/pipeline_transaction_test.h | 57 + .../sw/redis++/pipeline_transaction_test.hpp | 184 ++ .../test/src/sw/redis++/pubsub_test.h | 53 + .../test/src/sw/redis++/pubsub_test.hpp | 244 ++ .../test/src/sw/redis++/sanity_test.h | 76 + .../test/src/sw/redis++/sanity_test.hpp | 299 +++ .../test/src/sw/redis++/script_cmds_test.h | 49 + .../test/src/sw/redis++/script_cmds_test.hpp | 97 + .../test/src/sw/redis++/set_cmds_test.h | 53 + .../test/src/sw/redis++/set_cmds_test.hpp | 184 ++ .../test/src/sw/redis++/stream_cmds_test.h | 54 + .../test/src/sw/redis++/stream_cmds_test.hpp | 225 ++ .../test/src/sw/redis++/string_cmds_test.h | 57 + .../test/src/sw/redis++/string_cmds_test.hpp | 247 ++ .../test/src/sw/redis++/test_main.cpp | 303 +++ .../test/src/sw/redis++/threads_test.h | 51 + .../test/src/sw/redis++/threads_test.hpp | 147 ++ .../test/src/sw/redis++/utils.h | 96 + .../test/src/sw/redis++/zset_cmds_test.h | 61 + .../test/src/sw/redis++/zset_cmds_test.hpp | 350 +++ 96 files changed, 35078 insertions(+) create mode 100644 ext/redis-plus-plus-1.1.1/.gitignore create mode 100644 ext/redis-plus-plus-1.1.1/CMakeLists.txt create mode 100644 ext/redis-plus-plus-1.1.1/LICENSE create mode 100644 ext/redis-plus-plus-1.1.1/README.md create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_args.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_options.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection_pool.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/errors.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/pipeline.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.hpp create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis++.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.hpp create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.hpp create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/reply.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/sentinel.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards_pool.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/subscriber.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/transaction.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/utils.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/command.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/command.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/command_args.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/crc16.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.hpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis++.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.hpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.hpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/utils.h create mode 100644 ext/redis-plus-plus-1.1.1/test/CMakeLists.txt create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/test_main.cpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/utils.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.hpp diff --git a/ext/redis-plus-plus-1.1.1/.gitignore b/ext/redis-plus-plus-1.1.1/.gitignore new file mode 100644 index 000000000..259148fa1 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/.gitignore @@ -0,0 +1,32 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app diff --git a/ext/redis-plus-plus-1.1.1/CMakeLists.txt b/ext/redis-plus-plus-1.1.1/CMakeLists.txt new file mode 100644 index 000000000..ba069a62a --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/CMakeLists.txt @@ -0,0 +1,51 @@ +project(redis++) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + cmake_minimum_required(VERSION 3.0.0) +else() + cmake_minimum_required(VERSION 2.8.0) +endif() + +set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -W -Werror -fPIC") + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +set(PROJECT_SOURCE_DIR ${PROJECT_SOURCE_DIR}/src/sw/redis++) + +file(GLOB PROJECT_SOURCE_FILES "${PROJECT_SOURCE_DIR}/*.cpp") + +set(STATIC_LIB static) +#set(SHARED_LIB shared) + +add_library(${STATIC_LIB} STATIC ${PROJECT_SOURCE_FILES}) +# add_library(${SHARED_LIB} SHARED ${PROJECT_SOURCE_FILES}) + +# hiredis dependency +find_path(HIREDIS_HEADER hiredis) +target_include_directories(${STATIC_LIB} PUBLIC ${HIREDIS_HEADER}) +# target_include_directories(${SHARED_LIB} PUBLIC ${HIREDIS_HEADER}) + +#find_library(HIREDIS_LIB hiredis) +#target_link_libraries(${SHARED_LIB} ${HIREDIS_LIB}) + +set_target_properties(${STATIC_LIB} PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) +#set_target_properties(${SHARED_LIB} PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) + +set_target_properties(${STATIC_LIB} PROPERTIES CLEAN_DIRECT_OUTPUT 1) +#set_target_properties(${SHARED_LIB} PROPERTIES CLEAN_DIRECT_OUTPUT 1) + +# add_subdirectory(test) + + +# Install static lib. +install(TARGETS ${STATIC_LIB} + ARCHIVE DESTINATION lib) + +# Install shared lib. +#install(TARGETS ${SHARED_LIB} +# LIBRARY DESTINATION lib) + +#Install headers. +set(HEADER_PATH "sw/redis++") +file(GLOB HEADERS "${PROJECT_SOURCE_DIR}/*.h*") +install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/${HEADER_PATH}) diff --git a/ext/redis-plus-plus-1.1.1/LICENSE b/ext/redis-plus-plus-1.1.1/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ext/redis-plus-plus-1.1.1/README.md b/ext/redis-plus-plus-1.1.1/README.md new file mode 100644 index 000000000..8d2fd7d28 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/README.md @@ -0,0 +1,1776 @@ +# redis-plus-plus + +- [Overview](#overview) + - [Features](#features) +- [Installation](#installation) + - [Install hiredis](#install-hiredis) + - [Install redis-plus-plus](#install-redis-plus-plus) + - [Run Tests (Optional)](#run-tests-optional) + - [Use redis-plus-plus In Your Project](#use-redis-plus-plus-in-your-project) +- [Getting Started](#getting-started) +- [API Reference](#api-reference) + - [Connection](#connection) + - [Send Command to Redis Server](#send-command-to-redis-server) + - [Generic Command Interface](#generic-command-interface) + - [Publish/Subscribe](#publishsubscribe) + - [Pipeline](#pipeline) + - [Transaction](#transaction) + - [Redis Cluster](#redis-cluster) + - [Redis Sentinel](#redis-sentinel) + - [Redis Stream](#redis-stream) +- [Author](#author) + +## Overview + +This is a C++ client for Redis. It's based on [hiredis](https://github.com/redis/hiredis), and written in C++ 11. + +**NOTE**: I'm not a native speaker. So if the documentation is unclear, please feel free to open an issue or pull request. I'll response ASAP. + +### Features +- Most commands for Redis. +- Connection pool. +- Redis scripting. +- Thread safe unless otherwise stated. +- Redis publish/subscribe. +- Redis pipeline. +- Redis transaction. +- Redis Cluster. +- Redis Sentinel. +- STL-like interfaces. +- Generic command interface. + +## Installation + +### Install hiredis + +Since *redis-plus-plus* is based on *hiredis*, you should install *hiredis* first. The minimum version requirement for *hiredis* is **v0.12.1**, and you'd better use the latest release of *hiredis*. + +``` +git clone https://github.com/redis/hiredis.git + +cd hiredis + +make + +make install +``` + +By default, *hiredis* is installed at */usr/local*. If you want to install *hiredis* at non-default location, use the following commands to specify the installation path. + +``` +make PREFIX=/non/default/path + +make PREFIX=/non/default/path install +``` + +### Install redis-plus-plus + +*redis-plus-plus* is built with [CMAKE](https://cmake.org). + +``` +git clone https://github.com/sewenew/redis-plus-plus.git + +cd redis-plus-plus + +mkdir compile + +cd compile + +cmake -DCMAKE_BUILD_TYPE=Release .. + +make + +make install + +cd .. +``` + +If *hiredis* is installed at non-default location, you should use `CMAKE_PREFIX_PATH` to specify the installation path of *hiredis*. By default, *redis-plus-plus* is installed at */usr/local*. However, you can use `CMAKE_INSTALL_PREFIX` to install *redis-plus-plus* at non-default location. + +``` +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/path/to/hiredis -DCMAKE_INSTALL_PREFIX=/path/to/install/redis-plus-plus .. +``` + +### Run Tests (Optional) + +*redis-plus-plus* has been fully tested with the following compilers: + +``` +gcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC) +gcc version 5.5.0 20171010 (Ubuntu 5.5.0-12ubuntu1) +gcc version 6.5.0 20181026 (Ubuntu 6.5.0-2ubuntu1~18.04) +gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1) +gcc version 8.3.0 (Ubuntu 8.3.0-6ubuntu1~18.04.1) +clang version 3.9.1-19ubuntu1 (tags/RELEASE_391/rc2) +clang version 4.0.1-10 (tags/RELEASE_401/final) +clang version 5.0.1-4 (tags/RELEASE_501/final) +clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final) +clang version 7.0.0-3~ubuntu0.18.04.1 (tags/RELEASE_700/final) +Apple clang version 11.0.0 (clang-1100.0.33.8) +``` + +After compiling with cmake, you'll get a test program in *compile/test* directory: *compile/test/test_redis++*. + +In order to run the tests, you need to set up a Redis instance, and a Redis Cluster. Since the test program will send most of Redis commands to the server and cluster, you need to set up Redis of the latest version (by now, it's 5.0). Otherwise, the tests might fail. For example, if you set up Redis 4.0 for testing, the test program will fail when it tries to send the `ZPOPMAX` command (a Redis 5.0 command) to the server. If you want to run the tests with other Redis versions, you have to comment out commands that haven't been supported by your Redis, from test source files in *redis-plus-plus/test/src/sw/redis++/* directory. Sorry for the inconvenience, and I'll fix this problem to make the test program work with any version of Redis in the future. + +**NOTE**: The latest version of Redis is only a requirement for running the tests. In fact, you can use *redis-plus-plus* with Redis of any version, e.g. Redis 2.0, Redis 3.0, Redis 4.0, Redis 5.0. + +**NEVER** run the test program in production envronment, since the keys, which the test program reads or writes, might conflict with your application. + +In order to run tests with both Redis and Redis Cluster, you can run the test program with the following command: + +``` +./compile/test/test_redis++ -h host -p port -a auth -n cluster_node -c cluster_port +``` + +- *host* and *port* are the host and port number of the Redis instance. +- *cluster_node* and *cluster_port* are the host and port number of Redis Cluster. You only need to set the host and port number of a single node in the cluster, *redis-plus-plus* will find other nodes automatically. +- *auth* is the password of the Redis instance and Redis Cluster. The Redis instance and Redis Cluster must be configured with the same password. If there's no password configured, don't set this option. + +If you only want to run tests with Redis, you only need to specify *host*, *port* and *auth* options: + +``` +./compile/test/test_redis++ -h host -p port -a auth +``` + +Similarly, if you only want to run tests with Redis Cluster, just specify *cluster_node*, *cluster_port* and *auth* options: + +``` +./compile/test/test_redis++ -a auth -n cluster_node -c cluster_port +``` + +The test program will test running *redis-plus-plus* in multi-threads environment, and this test will cost a long time. If you want to skip it (not recommended), just comment out the following lines in *test/src/sw/redis++/test_main.cpp* file. + +```C++ +sw::redis::test::ThreadsTest threads_test(opts, cluster_node_opts); +threads_test.run(); +``` + +If all tests have been passed, the test program will print the following message: + +``` +Pass all tests +``` + +Otherwise, it prints the error message. + +#### Performance + +*redis-plus-plus* runs as fast as *hiredis*, since it's a wrapper of *hiredis*. You can run *test_redis++* in benchmark mode to check the performance in your environment. + +``` +./compile/test/test_redis++ -h host -p port -a auth -n cluster_node -c cluster_port -b -t thread_num -s connection_pool_size -r request_num -k key_len -v val_len +``` + +- *-b* option turns the test program into benchmark mode. +- *thread_num* specifies the number of worker threads. `10` by default. +- *connection_pool_size* specifies the size of the connection pool. `5` by default. +- *request_num* specifies the total number of requests sent to server for each test. `100000` by default. +- *key_len* specifies the length of the key for each operation. `10` by default. +- *val_len* specifies the length of the value. `10` by default. + +The bechmark will generate `100` random binary keys for testing, and the size of these keys is specified by *key_len*. When the benchmark runs, it will read/write with these keys. So **NEVER** run the test program in your production environment, otherwise, it might inaccidently delete your data. + +### Use redis-plus-plus In Your Project + +After compiling the code, you'll get both shared library and static library. Since *redis-plus-plus* depends on *hiredis*, you need to link both libraries to your Application. Also don't forget to specify the `-std=c++11` and thread-related option. + +#### Use Static Libraries + +Take gcc as an example. + +``` +g++ -std=c++11 -o app app.cpp /path/to/libredis++.a /path/to/libhiredis.a -pthread +``` + +If *hiredis* and *redis-plus-plus* are installed at non-default location, you should use `-I` option to specify the header path. + +``` +g++ -std=c++11 -I/non-default/install/include/path -o app app.cpp /path/to/libredis++.a /path/to/libhiredis.a -pthread +``` + +#### Use Shared Libraries + +``` +g++ -std=c++11 -o app app.cpp -lredis++ -lhiredis -pthread +``` + +If *hiredis* and *redis-plus-plus* are installed at non-default location, you should use `-I` and `-L` options to specify the header and library paths. + +``` +g++ -std=c++11 -I/non-default/install/include/path -L/non-default/install/lib/path -o app app.cpp -lredis++ -lhiredis -pthread +``` + +When linking with shared libraries, and running your application, you might get the following error message: + +``` +error while loading shared libraries: xxx: cannot open shared object file: No such file or directory. +``` + +That's because the linker cannot find the shared libraries. In order to solve the problem, you can add the path where you installed *hiredis* and *redis-plus-plus* libraries, to `LD_LIBRARY_PATH` environment variable. For example: + +``` +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib +``` + +Check [this StackOverflow question](https://stackoverflow.com/questions/480764) for details on how to solve the problem. + +#### Build With Cmake + +If you're using cmake to build your application, you need to add *hiredis* and *redis-plus-plus* dependencies in your *CMakeLists.txt*: + +```CMake +# <------------ add hiredis dependency ---------------> +find_path(HIREDIS_HEADER hiredis) +target_include_directories(target PUBLIC ${HIREDIS_HEADER}) + +find_library(HIREDIS_LIB hiredis) +target_link_libraries(target ${HIREDIS_LIB}) + +# <------------ add redis-plus-plus dependency --------------> +# NOTE: this should be *sw* NOT *redis++* +find_path(REDIS_PLUS_PLUS_HEADER sw) +target_include_directories(target PUBLIC ${REDIS_PLUS_PLUS_HEADER}) + +find_library(REDIS_PLUS_PLUS_LIB redis++) +target_link_libraries(target ${REDIS_PLUS_PLUS_LIB}) +``` + +See [this issue](https://github.com/sewenew/redis-plus-plus/issues/5) for a complete example of *CMakeLists.txt*. + +Also, if you installed *hiredis* and *redis-plus-plus* at non-default location, you need to run cmake with `CMAKE_PREFIX_PATH` option to specify the installation path of these two libraries. + +``` +cmake -DCMAKE_PREFIX_PATH=/installation/path/to/the/two/libs .. +``` + +## Getting Started + +```C++ +#include + +using namespace sw::redis; + +try { + // Create an Redis object, which is movable but NOT copyable. + auto redis = Redis("tcp://127.0.0.1:6379"); + + // ***** STRING commands ***** + + redis.set("key", "val"); + auto val = redis.get("key"); // val is of type OptionalString. See 'API Reference' section for details. + if (val) { + // Dereference val to get the returned value of std::string type. + std::cout << *val << std::endl; + } // else key doesn't exist. + + // ***** LIST commands ***** + + // std::vector to Redis LIST. + std::vector vec = {"a", "b", "c"}; + redis.rpush("list", vec.begin(), vec.end()); + + // std::initializer_list to Redis LIST. + redis.rpush("list", {"a", "b", "c"}); + + // Redis LIST to std::vector. + vec.clear(); + redis.lrange("list", 0, -1, std::back_inserter(vec)); + + // ***** HASH commands ***** + + redis.hset("hash", "field", "val"); + + // Another way to do the same job. + redis.hset("hash", std::make_pair("field", "val")); + + // std::unordered_map to Redis HASH. + std::unordered_map m = { + {"field1", "val1"}, + {"field2", "val2"} + }; + redis.hmset("hash", m.begin(), m.end()); + + // Redis HASH to std::unordered_map. + m.clear(); + redis.hgetall("hash", std::inserter(m, m.begin())); + + // Get value only. + // NOTE: since field might NOT exist, so we need to parse it to OptionalString. + std::vector vals; + redis.hmget("hash", {"field1", "field2"}, std::back_inserter(vals)); + + // ***** SET commands ***** + + redis.sadd("set", "m1"); + + // std::unordered_set to Redis SET. + std::unordered_set set = {"m2", "m3"}; + redis.sadd("set", set.begin(), set.end()); + + // std::initializer_list to Redis SET. + redis.sadd("set", {"m2", "m3"}); + + // Redis SET to std::unordered_set. + set.clear(); + redis.smembers("set", std::inserter(set, set.begin())); + + if (redis.sismember("set", "m1")) { + std::cout << "m1 exists" << std::endl; + } // else NOT exist. + + // ***** SORTED SET commands ***** + + redis.zadd("sorted_set", "m1", 1.3); + + // std::unordered_map to Redis SORTED SET. + std::unordered_map scores = { + {"m2", 2.3}, + {"m3", 4.5} + }; + redis.zadd("sorted_set", scores.begin(), scores.end()); + + // Redis SORTED SET to std::unordered_map. + scores.clear(); + redis.zrangebyscore("sorted_set", + UnboundedInterval{}, // (-inf, +inf) + std::inserter(scores, scores.begin())); + + // Only get member names: + // pass an inserter of std::vector type as output parameter. + std::vector without_score; + redis.zrangebyscore("sorted_set", + BoundedInterval(1.5, 3.4, BoundType::CLOSED), // [1.5, 3.4] + std::back_inserter(without_score)); + + // Get both member names and scores: + // pass an inserter of std::unordered_map as output parameter. + std::unordered_map with_score; + redis.zrangebyscore("sorted_set", + BoundedInterval(1.5, 3.4, BoundType::LEFT_OPEN), // (1.5, 3.4] + std::inserter(with_score, with_score.end())); + + // ***** SCRIPTING commands ***** + + // Script returns a single element. + auto num = redis.eval("return 1", {}, {}); + + // Script returns an array of elements. + std::vector nums; + redis.eval("return {ARGV[1], ARGV[2]}", {}, {"1", "2"}, std::back_inserter(nums)); + + // ***** Pipeline ***** + + // Create a pipeline. + auto pipe = redis.pipeline(); + + // Send mulitple commands and get all replies. + auto pipe_replies = pipe.set("key", "value") + .get("key") + .rename("key", "new-key") + .rpush("list", {"a", "b", "c"}) + .lrange("list", 0, -1) + .exec(); + + // Parse reply with reply type and index. + auto set_cmd_result = pipe_replies.get(0); + + auto get_cmd_result = pipe_replies.get(1); + + // rename command result + pipe_replies.get(2); + + auto rpush_cmd_result = pipe_replies.get(3); + + std::vector lrange_cmd_result; + pipe_replies.get(4, back_inserter(lrange_cmd_result)); + + // ***** Transaction ***** + + // Create a transaction. + auto tx = redis.transaction(); + + // Run multiple commands in a transaction, and get all replies. + auto tx_replies = tx.incr("num0") + .incr("num1") + .mget({"num0", "num1"}) + .exec(); + + // Parse reply with reply type and index. + auto incr_result0 = tx_replies.get(0); + + auto incr_result1 = tx_replies.get(1); + + std::vector mget_cmd_result; + tx_replies.get(2, back_inserter(mget_cmd_result)); + + // ***** Generic Command Interface ***** + + // There's no *Redis::client_getname* interface. + // But you can use *Redis::command* to get the client name. + val = redis.command("client", "getname"); + if (val) { + std::cout << *val << std::endl; + } + + // Same as above. + auto getname_cmd_str = {"client", "getname"}; + val = redis.command(getname_cmd_str.begin(), getname_cmd_str.end()); + + // There's no *Redis::sort* interface. + // But you can use *Redis::command* to send sort the list. + std::vector sorted_list; + redis.command("sort", "list", "ALPHA", std::back_inserter(sorted_list)); + + // Another *Redis::command* to do the same work. + auto sort_cmd_str = {"sort", "list", "ALPHA"}; + redis.command(sort_cmd_str.begin(), sort_cmd_str.end(), std::back_inserter(sorted_list)); + + // ***** Redis Cluster ***** + + // Create a RedisCluster object, which is movable but NOT copyable. + auto redis_cluster = RedisCluster("tcp://127.0.0.1:7000"); + + // RedisCluster has similar interfaces as Redis. + redis_cluster.set("key", "value"); + val = redis_cluster.get("key"); + if (val) { + std::cout << *val << std::endl; + } // else key doesn't exist. + + // Keys with hash-tag. + redis_cluster.set("key{tag}1", "val1"); + redis_cluster.set("key{tag}2", "val2"); + redis_cluster.set("key{tag}3", "val3"); + + std::vector hash_tag_res; + redis_cluster.mget({"key{tag}1", "key{tag}2", "key{tag}3"}, + std::back_inserter(hash_tag_res)); + +} catch (const Error &e) { + // Error handling. +} +``` + +## API Reference + +### Connection + +`Redis` class maintains a connection pool to Redis server. If the connection is broken, `Redis` reconnects to Redis server automatically. + +You can initialize a `Redis` instance with `ConnectionOptions` and `ConnectionPoolOptions`. `ConnectionOptions` specifies options for connection to Redis server, and `ConnectionPoolOptions` specifies options for conneciton pool. `ConnectionPoolOptions` is optional. If not specified, `Redis` maintains a single connection to Redis server. + +```C++ +ConnectionOptions connection_options; +connection_options.host = "127.0.0.1"; // Required. +connection_options.port = 6666; // Optional. The default port is 6379. +connection_options.password = "auth"; // Optional. No password by default. +connection_options.db = 1; // Optional. Use the 0th database by default. + +// Optional. Timeout before we successfully send request to or receive response from redis. +// By default, the timeout is 0ms, i.e. never timeout and block until we send or receive successfuly. +// NOTE: if any command is timed out, we throw a TimeoutError exception. +connection_options.socket_timeout = std::chrono::milliseconds(200); + +// Connect to Redis server with a single connection. +Redis redis1(connection_options); + +ConnectionPoolOptions pool_options; +pool_options.size = 3; // Pool size, i.e. max number of connections. + +// Connect to Redis server with a connection pool. +Redis redis2(connection_options, pool_options); +``` + +See [ConnectionOptions](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/connection.h#L40) and [ConnectionPoolOptions](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/connection_pool.h#L30) for more options. + +**NOTE**: `Redis` class is movable but NOT copyable. + +```C++ +// auto redis3 = redis1; // this won't compile. + +// But it's movable. +auto redis3 = std::move(redis1); +``` + +*redis-plus-plus* also supports connecting to Redis server with Unix Domain Socket. + +```C++ +ConnectionOptions options; +options.type = ConnectionType::UNIX; +options.path = "/path/to/socket"; +Redis redis(options); +``` + +You can also connect to Redis server with a URI. However, in this case, you can only specify *host* and *port*, or *Unix Domain Socket path*. In order to specify other options, you need to use `ConnectionOptions` and `ConnectionPoolOptions`. + +```C++ +// Single connection to the given host and port. +Redis redis1("tcp://127.0.0.1:6666"); + +// Use default port, i.e. 6379. +Redis redis2("tcp://127.0.0.1"); + +// Connect to Unix Domain Socket. +Redis redis3("unix://path/to/socket"); +``` + +#### Lazily Create Connection + +Connections in the pool are lazily created. When the connection pool is initialized, i.e. the constructor of `Redis`, `Redis` does NOT connect to the server. Instead, it connects to the server only when you try to send command. In this way, we can avoid unnecessary connections. So if the pool size is 5, but the number of max concurrent connections is 3, there will be only 3 connections in the pool. + +#### Connection Failure + +You don't need to check whether `Redis` object connects to server successfully. If `Redis` fails to create a connection to Redis server, or the connection is broken at some time, it throws an exception of type `Error` when you try to send command with `Redis`. Even when you get an exception, i.e. the connection is broken, you don't need to create a new `Redis` object. You can reuse the `Redis` object to send commands, and the `Redis` object will try to reconnect to server automatically. If it reconnects successfully, it sends command to server. Otherwise, it throws an exception again. + +See the [Exception section](#exception) for details on exceptions. + +#### Reuse Redis object As Much As Possible + +It's NOT cheap to create a `Redis` object, since it will create new connections to Redis server. So you'd better reuse `Redis` object as much as possible. Also, it's safe to call `Redis`' member functions in multi-thread environment, and you can share `Redis` object in multiple threads. + +```C++ +// This is GOOD practice. +auto redis = Redis("tcp://127.0.0.1"); +for (auto idx = 0; idx < 100; ++idx) { + // Reuse the Redis object in the loop. + redis.set("key", "val"); +} + +// This is VERY BAD! It's very inefficient. +// NEVER DO IT!!! +for (auto idx = 0; idx < 100; ++idx) { + // Create a new Redis object for each iteration. + auto redis = Redis("tcp://127.0.0.1"); + redis.set("key", "val"); +} +``` + +### Send Command to Redis Server + +You can send [Redis commands](https://redis.io/commands) through `Redis` object. `Redis` has one or more (overloaded) methods for each Redis command. The method has the same (lowercased) name as the corresponding command. For example, we have 3 overload methods for the `DEL key [key ...]` command: + +```C++ +// Delete a single key. +long long Redis::del(const StringView &key); + +// Delete a batch of keys: [first, last). +template +long long Redis::del(Input first, Input last); + +// Delete keys in the initializer_list. +template +long long Redis::del(std::initializer_list il); +``` + +With input parameters, these methods build a Redis command based on [Redis protocol](https://redis.io/topics/protocol), and send the command to Redis server. Then synchronously receive the reply, parse it, and return to the caller. + +Let's take a closer look at these methods' parameters and return values. + +#### Parameter Type + +Most of these methods have the same parameters as the corresponding commands. The following is a list of parameter types: + +| Parameter Type | Explaination | Example | Note | +| :------------: | ------------ | ------- | ---- | +| **StringView** | Parameters of string type. Normally used for key, value, member name, field name and so on | ***bool Redis::hset(const StringView &key, const StringView &field, const StringView &val)*** | See the [StringView section](#stringview) for details on `StringView` | +| **long long** | Parameters of integer type. Normally used for index (e.g. list commands) or integer | ***void ltrim(const StringView &key, long long start, long long stop)***
***long long decrby(const StringView &key, long long decrement)*** | | +| **double** | Parameters of floating-point type. Normally used for score (e.g. sorted set commands) or number of floating-point type | ***double incrbyfloat(const StringView &key, double increment)*** | | +| **std::chrono::duration**
**std::chrono::time_point** | Time-related parameters | ***bool expire(const StringView &key, const std::chrono::seconds &timeout)***
***bool expireat(const StringView &key, const std::chrono::time_point &tp)*** | | +| **std::pair** | Used for Redis hash's (field, value) pair | ***bool hset(const StringView &key, const std::pair &item)*** | | +| **std::pair** | Used for Redis geo's (longitude, latitude) pair | ***OptionalLongLong georadius(const StringView &key, const std::pair &location, double radius, GeoUnit unit, const StringView &destination, bool store_dist, long long count)*** | | +| **pair of iterators** | Use a pair of iterators to specify a range of input, so that we can pass the data in a STL container to these methods | ***template < typename Input >***
***long long del(Input first, Input last)*** | Throw an exception, if it's an empty range, i.e. *first == last* | +| **std::initializer_list< T >** | Use an initializer list to specify a batch of input | ***template < typename T >***
***long long del(std::initializer_list< T > il)*** | | +| **some options** | Options for some commands | ***UpdateType***, ***template < typename T > class BoundedInterval*** | See [command_options.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/command_options.h) for details | + +##### StringView + +[std::string_view](http://en.cppreference.com/w/cpp/string/basic_string_view) is a good option for the type of string parameters. However, by now, not all compilers support `std::string_view`. So we wrote a [simple version](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/utils.h#L48), i.e. `StringView`. Since there are conversions from `std::string` and c-style string to `StringView`, you can just pass `std::string` or c-style string to methods that need a `StringView` parameter. + +```C++ +// bool Redis::hset(const StringView &key, const StringView &field, const StringView &val) + +// Pass c-style string to StringView. +redis.hset("key", "field", "value"); + +// Pass std::string to StringView. +std::string key = "key"; +std::string field = "field"; +std::string val = "val"; +redis.hset(key, field, val); + +// Mix std::string and c-style string. +redis.hset(key, field, "value"); +``` + +#### Return Type + +[Redis protocol](https://redis.io/topics/protocol) defines 5 kinds of replies: +- *Status Reply*: Also known as *Simple String Reply*. It's a non-binary string reply. +- *Bulk String Reply*: Binary safe string reply. +- *Integer Reply*: Signed integer reply. Large enough to hold `long long`. +- *Array Reply*: (Nested) Array reply. +- *Error Reply*: Non-binary string reply that gives error info. + +Also these replies might be *NULL*. For instance, when you try to `GET` the value of a nonexistent key, Redis returns a *NULL Bulk String Reply*. + +As we mentioned above, replies are parsed into return values of these methods. The following is a list of return types: + +| Return Type | Explaination | Example | Note | +| :---------: | ------------ | ------- | ---- | +| **void** | *Status Reply* that should always return a string of "OK" | *RENAME*, *SETEX* | | +| **std::string** | *Status Reply* that NOT always return "OK", and *Bulk String Reply* | *PING*, *INFO* | | +| **bool** | *Integer Reply* that always returns 0 or 1 | *EXPIRE*, *HSET* | See the [Boolean Return Value section](#boolean-return-value) for the meaning of a boolean return value | +| **long long** | *Integer Reply* that not always return 0 or 1 | *DEL*, *APPEND* | | +| **double** | *Bulk String Reply* that represents a double | *INCRBYFLOAT*, *ZINCRBY* | | +| **std::pair** | *Array Reply* with exactly 2 elements. Since the return value is always an array of 2 elements, we return the 2 elements as a `std::pair`'s first and second elements | *BLPOP* | | +| **std::tuple** | *Array Reply* with fixed length and has more than 2 elements. Since length of the returned array is fixed, we return the array as a `std::tuple` | *BZPOPMAX* | | +| **output iterator** | General *Array Reply* with non-fixed/dynamic length. We use STL-like interface to return this kind of array replies, so that you can insert the return value into a STL container easily | *MGET*, *LRANGE* | Also, sometimes the type of output iterator decides which options to send with the command. See the [Examples section](#command-overloads) for details | +| **Optional< T >** | For any reply of type `T` that might be *NULL* | *GET*, *LPOP*, *BLPOP*, *BZPOPMAX* | See the [Optional section](#optional) for details on `Optional` | + +##### Boolean Return Value + +The return type of some methods, e.g. `EXPIRE`, `HSET`, is `bool`. If the method returns `false`, it DOES NOT mean that `Redis` failed to send the command to Redis server. Instead, it means that Redis server returns an *Integer Reply*, and the value of the reply is `0`. Accordingly, if the method returns `true`, it means that Redis server returns an *Integer Reply*, and the value of the reply is `1`. You can +check [Redis commands manual](http://redis.io/commands) for what do `0` and `1` stand for. + +For example, when we send `EXPIRE` command to Redis server, it returns `1` if the timeout was set, and it returns `0` if the key doesn't exist. Accordingly, if the timeout was set, `Redis::expire` returns `true`, and if the key doesn't exist, `Redis::expire` returns `false`. + +So, never use the return value to check if the command has been successfully sent to Redis server. Instead, if `Redis` failed to send command to server, it throws an exception of type `Error`. See the [Exception section](#exception) for details on exceptions. + +##### Optional + +[std::optional](http://en.cppreference.com/w/cpp/utility/optional) is a good option for return type, if Redis might return *NULL REPLY*. Again, since not all compilers support `std::optional` so far, we implement our own [simple version](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/utils.h#L85), i.e. `Optional`. + +Take the [GET](https://redis.io/commands/get) and [MGET](https://redis.io/commands/mget) commands for example: + +```C++ +// Or just: auto val = redis.get("key"); +Optional val = redis.get("key"); + +// Optional has a conversion to bool. +// If it's NOT a null Optional object, it's converted to true. +// Otherwise, it's converted to false. +if (val) { + // Key exists. Dereference val to get the string result. + std::cout << *val << std::endl; +} else { + // Redis server returns a NULL Bulk String Reply. + // It's invalid to dereference a null Optional object. + std::cout << "key doesn't exist." << std::endl; +} + +std::vector> values; +redis.mget({"key1", "key2", "key3"}, std::back_inserter(values)); +for (const auto &val : values) { + if (val) { + // Key exist, process the value. + } +} +``` + +We also have some typedefs for some commonly used `Optional`: + +```C++ +using OptionalString = Optional; + +using OptionalLongLong = Optional; + +using OptionalDouble = Optional; + +using OptionalStringPair = Optional>; +``` + +#### Exception + +`Redis` throws exceptions if it receives an *Error Reply* or something bad happens, e.g. failed to create a connection to server, or connection to server is broken. All exceptions derived from `Error` class. See [errors.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/errors.h) for details. + +- `Error`: Generic error. It's also the base class of other exceptions. +- `IoError`: There's some IO error with the connection. +- `TimeoutError`: Read or write operation was timed out. It's a derived class of `IoError`. +- `ClosedError`: Redis server closed the connection. +- `ProtoError`: The command or reply is invalid, and we cannot process it with Redis protocol. +- `OomError`: *hiredis* library got an out-of-memory error. +- `ReplyError`: Redis server returned an error reply, e.g. we try to call `redis::lrange` on a Redis hash. +- `WatchError`: Watched key has been modified. See [Watch section](#watch) for details. + +**NOTE**: *NULL REPLY*` is not taken as an exception. For example, if we try to `GET` a non-existent key, we'll get a *NULL Bulk String Reply*. Instead of throwing an exception, we return the *NULL REPLY* as a null `Optional` object. Also see [Optional section](#optional). + +#### Examples + +Let's see some examples on how to send commands to Redis server. + +##### Various Parameter Types + +```C++ +// ***** Parameters of StringView type ***** + +// Implicitly construct StringView with c-style string. +redis.set("key", "value"); + +// Implicitly construct StringView with std::string. +std::string key("key"); +std::string val("value"); +redis.set(key, val); + +// Explicitly pass StringView as parameter. +std::vector large_data; +// Avoid copying. +redis.set("key", StringView(large_data.data(), large_data.size())); + +// ***** Parameters of long long type ***** + +// For index. +redis.bitcount(key, 1, 3); + +// For number. +redis.incrby("num", 100); + +// ***** Parameters of double type ***** + +// For score. +redis.zadd("zset", "m1", 2.5); +redis.zadd("zset", "m2", 3.5); +redis.zadd("zset", "m3", 5); + +// For (longitude, latitude). +redis.geoadd("geo", std::make_tuple("member", 13.5, 15.6)); + +// ***** Time-related parameters ***** + +using namespace std::chrono; + +redis.expire(key, seconds(1000)); + +auto tp = time_point_cast(system_clock::now() + seconds(100)); +redis.expireat(key, tp); + +// ***** Some options for commands ***** + +if (redis.set(key, "value", milliseconds(100), UpdateType::NOT_EXIST)) { + std::cout << "set OK" << std::endl; +} + +redis.linsert("list", InsertPosition::BEFORE, "pivot", "val"); + +std::vector res; + +// (-inf, inf) +redis.zrangebyscore("zset", UnboundedInterval{}, std::back_inserter(res)); + +// [3, 6] +redis.zrangebyscore("zset", + BoundedInterval(3, 6, BoundType::CLOSED), + std::back_inserter(res)); + +// (3, 6] +redis.zrangebyscore("zset", + BoundedInterval(3, 6, BoundType::LEFT_OPEN), + std::back_inserter(res)); + +// (3, 6) +redis.zrangebyscore("zset", + BoundedInterval(3, 6, BoundType::OPEN), + std::back_inserter(res)); + +// [3, 6) +redis.zrangebyscore("zset", + BoundedInterval(3, 6, BoundType::RIGHT_OPEN), + std::back_inserter(res)); + +// [3, +inf) +redis.zrangebyscore("zset", + LeftBoundedInterval(3, BoundType::RIGHT_OPEN), + std::back_inserter(res)); + +// (3, +inf) +redis.zrangebyscore("zset", + LeftBoundedInterval(3, BoundType::OPEN), + std::back_inserter(res)); + +// (-inf, 6] +redis.zrangebyscore("zset", + RightBoundedInterval(6, BoundType::LEFT_OPEN), + std::back_inserter(res)); + +// (-inf, 6) +redis.zrangebyscore("zset", + RightBoundedInterval(6, BoundType::OPEN), + std::back_inserter(res)); + +// ***** Pair of iterators ***** + +std::vector> kvs = {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}}; +redis.mset(kvs.begin(), kvs.end()); + +std::unordered_map kv_map = {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}}; +redis.mset(kv_map.begin(), kv_map.end()); + +std::unordered_map str_map = {{"f1", "v1"}, {"f2", "v2"}, {"f3", "v3"}}; +redis.hmset("hash", str_map.begin(), str_map.end()); + +std::unordered_map score_map = {{"m1", 20}, {"m2", 12.5}, {"m3", 3.14}}; +redis.zadd("zset", score_map.begin(), score_map.end()); + +std::vector keys = {"k1", "k2", "k3"}; +redis.del(keys.begin(), keys.end()); + +// ***** Parameters of initializer_list type ***** + +redis.mset({ + std::make_pair("k1", "v1"), + std::make_pair("k2", "v2"), + std::make_pair("k3", "v3") +}); + +redis.hmset("hash", + { + std::make_pair("f1", "v1"), + std::make_pair("f2", "v2"), + std::make_pair("f3", "v3") + }); + +redis.zadd("zset", + { + std::make_pair("m1", 20.0), + std::make_pair("m2", 34.5), + std::make_pair("m3", 23.4) + }); + +redis.del({"k1", "k2", "k3"}); +``` + +##### Various Return Types + +```C++ +// ***** Return void ***** + +redis.save(); + +// ***** Return std::string ***** + +auto info = redis.info(); + +// ***** Return bool ***** + +if (!redis.expire("nonexistent", std::chrono::seconds(100))) { + std::cerr << "key doesn't exist" << std::endl; +} + +if (redis.setnx("key", "val")) { + std::cout << "set OK" << std::endl; +} + +// ***** Return long long ***** + +auto len = redis.strlen("key"); +auto num = redis.del({"a", "b", "c"}); +num = redis.incr("a"); + +// ***** Return double ***** + +auto real = redis.incrbyfloat("b", 23.4); +real = redis.hincrbyfloat("c", "f", 34.5); + +// ***** Return Optional, i.e. OptionalString ***** + +auto os = redis.get("kk"); +if (os) { + std::cout << *os << std::endl; +} else { + std::cerr << "key doesn't exist" << std::endl; +} + +os = redis.spop("set"); +if (os) { + std::cout << *os << std::endl; +} else { + std::cerr << "set is empty" << std::endl; +} + +// ***** Return Optional, i.e. OptionalLongLong ***** + +auto oll = redis.zrank("zset", "mem"); +if (oll) { + std::cout << "rank is " << *oll << std::endl; +} else { + std::cerr << "member doesn't exist" << std::endl; +} + +// ***** Return Optional, i.e. OptionalDouble ***** + +auto ob = redis.zscore("zset", "m1"); +if (ob) { + std::cout << "score is " << *ob << std::endl; +} else { + std::cerr << "member doesn't exist" << std::endl; +} + +// ***** Return Optional> ***** + +auto op = redis.blpop({"list1", "list2"}, std::chrono::seconds(2)); +if (op) { + std::cout << "key is " << op->first << ", value is " << op->second << std::endl; +} else { + std::cerr << "timeout" << std::endl; +} + +// ***** Output iterators ***** + +std::vector os_vec; +redis.mget({"k1", "k2", "k3"}, std::back_inserter(os_vec)); + +std::vector s_vec; +redis.lrange("list", 0, -1, std::back_inserter(s_vec)); + +std::unordered_map hash; +redis.hgetall("hash", std::inserter(hash, hash.end())); +// You can also save the result in a vecotr of string pair. +std::vector> hash_vec; +redis.hgetall("hash", std::back_inserter(hash_vec)); + +std::unordered_set str_set; +redis.smembers("s1", std::inserter(str_set, str_set.end())); +// You can also save the result in a vecotr of string. +s_vec.clear(); +redis.smembers("s1", std::back_inserter(s_vec)); +``` + +##### SCAN Commands + +```C++ +auto cursor = 0LL; +auto pattern = "*pattern*"; +auto count = 5; +std::vector scan_vec; +while (true) { + cursor = redis.scan(cursor, pattern, count, std::back_inserter(scan_vec)); + // Default pattern is "*", and default count is 10 + // cursor = redis.scan(cursor, std::back_inserter(scan_vec)); + + if (cursor == 0) { + break; + } +} +``` + +##### Command Overloads + +Sometimes the type of output iterator decides which options to send with the command. + +```C++ +// If the output iterator is an iterator of a container of string, +// we send *ZRANGE* command without the *WITHSCORES* option. +std::vector members; +redis.zrange("list", 0, -1, std::back_inserter(members)); + +// If it's an iterator of a container of a pair, +// we send *ZRANGE* command with *WITHSCORES* option. +std::unordered_map res_with_score; +redis.zrange("list", 0, -1, std::inserter(res_with_score, res_with_score.end())); + +// The above examples also apply to other command with the *WITHSCORES* options, +// e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + +// Another example is the *GEORADIUS* command. + +// Only get members. +members.clear(); +redis.georadius("geo", + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(members)); + +// If the iterator is an iterator of a container of tuple, +// we send the *GEORADIUS* command with *WITHDIST* option. +std::vector> mem_with_dist; +redis.georadius("geo", + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(mem_with_dist)); + +// If the iterator is an iterator of a container of tuple, +// we send the *GEORADIUS* command with *WITHDIST* and *WITHHASH* options. +std::vector> mem_with_dist_hash; +redis.georadius("geo", + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(mem_with_dist_hash)); + +// If the iterator is an iterator of a container of +// tuple, double>, +// we send the *GEORADIUS* command with *WITHHASH*, *WITHCOORD* and *WITHDIST* options. +std::vector> mem_with_hash_coord_dist; +redis.georadius("geo", + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(mem_with_hash_coord_dist)); +``` + +Please see [redis.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/redis.h) for more API references, and see the [tests](https://github.com/sewenew/redis-plus-plus/tree/master/test/src/sw/redis%2B%2B) for more examples. + +### Generic Command Interface + +There're too many Redis commands, we haven't implemented all of them. However, you can use the generic `Redis::command` methods to send any commands to Redis. Unlike other client libraries, `Redis::command` doesn't use format string to combine command arguments into a command string. Instead, you can directly pass command arguments of `StringView` type or arithmetic type as parameters of `Redis::command`. For the reason why we don't use format string, please see [this discussion](https://github.com/sewenew/redis-plus-plus/pull/2). + +```C++ +auto redis = Redis("tcp://127.0.0.1"); + +// Redis class doesn't have built-in *CLIENT SETNAME* method. +// However, you can use Redis::command to send the command manually. +redis.command("client", "setname", "name"); +auto val = redis.command("client", "getname"); +if (val) { + std::cout << *val << std::endl; +} + +// NOTE: the following code is for example only. In fact, Redis has built-in +// methods for the following commands. + +// Arguments of the command can be strings. +// NOTE: for SET command, the return value is NOT always void, I'll explain latter. +redis.command("set", "key", "100"); + +// Arguments of the command can be a combination of strings and integers. +auto num = redis.command("incrby", "key", 1); + +// Argument can also be double. +auto real = redis.command("incrbyfloat", "key", 2.3); + +// Even the key of the command can be of arithmetic type. +redis.command("set", 100, "value"); + +val = redis.command("get", 100); + +// If the command returns an array of elements. +std::vector result; +redis.command("mget", "k1", "k2", "k3", std::back_inserter(result)); + +// Or just parse it into a vector. +result = redis.command>("mget", "k1", "k2", "k3"); + +// Arguments of the command can be a range of strings. +auto set_cmd_strs = {"set", "key", "value"}; +redis.command(set_cmd_strs.begin(), set_cmd_strs.end()); + +auto get_cmd_strs = {"get", "key"}; +val = redis.command(get_cmd_strs.begin(), get_cmd_strs.end()); + +// If it returns an array of elements. +result.clear(); +auto mget_cmd_strs = {"mget", "key1", "key2"}; +redis.command(mget_cmd_strs.begin(), mget_cmd_strs.end(), std::back_inserter(result)); +``` + +**NOTE**: The name of some Redis commands is composed with two strings, e.g. *CLIENT SETNAME*. In this case, you need to pass these two strings as two arguments for `Redis::command`. + +```C++ +// This is GOOD. +redis.command("client", "setname", "name"); + +// This is BAD, and will fail to send command to Redis server. +// redis.command("client setname", "name"); +``` + +As I mentioned in the comments, the `SET` command not always returns `void`. Because if you try to set a (key, value) pair with *NX* or *XX* option, you might fail, and Redis will return a *NULL REPLY*. Besides the `SET` command, there're other commands whose return value is NOT a fixed type, you need to parse it by yourself. For example, `Redis::set` method rewrite the reply of `SET` command, and make it return `bool` type, i.e. if no *NX* or *XX* option specified, Redis server will always return an "OK" string, and `Redis::set` returns `true`; if *NX* or *XX* specified, and Redis server returns a *NULL REPLY*, `Redis::set` returns `false`. + +So `Redis` class also has other overloaded `command` methods, these methods return a `ReplyUPtr`, i.e. `std::unique_ptr`, object. Normally you don't need to parse it manually. Instead, you only need to pass the reply to `template T reply::parse(redisReply &)` to get a value of type `T`. Check the [Return Type section](#return-type) for valid `T` types. If the command returns an array of elements, besides calling `reply::parse` to parse the reply to an STL container, you can also call `template reply::to_array(redisReply &reply, Output output)` to parse the result into an array or STL container with an output iterator. + +Let's rewrite the above examples: + +```C++ +auto redis = Redis("tcp://127.0.0.1"); + +redis.command("client", "setname", "name"); +auto r = redis.command("client", "getname"); +assert(r); + +// If the command returns a single element, +// use `reply::parse(redisReply&)` to parse it. +auto val = reply::parse(*r); +if (val) { + std::cout << *val << std::endl; +} + +// Arguments of the command can be strings. +redis.command("set", "key", "100"); + +// Arguments of the command can be a combination of strings and integers. +r = redis.command("incrby", "key", 1); +auto num = reply::parse(*r); + +// Argument can also be double. +r = redis.command("incrbyfloat", "key", 2.3); +auto real = reply::parse(*r); + +// Even the key of the command can be of arithmetic type. +redis.command("set", 100, "value"); + +r = redis.command("get", 100); +val = reply::parse(*r); + +// If the command returns an array of elements. +r = redis.command("mget", "k1", "k2", "k3"); +// Use `reply::to_array(redisReply&, OutputIterator)` to parse the result into an STL container. +std::vector result; +reply::to_array(*r, std::back_inserter(result)); + +// Or just call `reply::parse` to parse it into vector. +result = reply::parse>(*r); + +// Arguments of the command can be a range of strings. +auto get_cmd_strs = {"get", "key"}; +r = redis.command(get_cmd_strs.begin(), get_cmd_strs.end()); +val = reply::parse(*r); + +// If it returns an array of elements. +result.clear(); +auto mget_cmd_strs = {"mget", "key1", "key2"}; +r = redis.command(mget_cmd_strs.begin(), mget_cmd_strs.end()); +reply::to_array(*r, std::back_inserter(result)); +``` + +In fact, there's one more `Redis::command` method: + +```C++ +template +auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; +``` + +However, this method exposes some implementation details, and is only for internal use. You should NOT use this method. + +### Publish/Subscribe + +You can use `Redis::publish` to publish messages to channels. `Redis` randomly picks a connection from the underlying connection pool, and publishes message with that connection. So you might publish two messages with two different connections. + +When you subscribe to a channel with a connection, all messages published to the channel are sent back to that connection. So there's NO `Redis::subscribe` method. Instead, you can call `Redis::subscriber` to create a `Subscriber` and the `Subscriber` maintains a connection to Redis. The underlying connection is a new connection, NOT picked from the connection pool. This new connection has the same `ConnectionOptions` as the `Redis` object. + +With `Subscriber`, you can call `Subscriber::subscribe`, `Subscriber::unsubscribe`, `Subscriber::psubscribe` and `Subscriber::punsubscribe` to send *SUBSCRIBE*, *UNSUBSCRIBE*, *PSUBSCRIBE* and *PUNSUBSCRIBE* commands to Redis. + +#### Thread Safety + +`Subscriber` is NOT thread-safe. If you want to call its member functions in multi-thread environment, you need to synchronize between threads manually. + +#### Subscriber Callbacks + +There are 6 kinds of messages: +- *MESSAGE*: message sent to a channel. +- *PMESSAGE*: message sent to channels of a given pattern. +- *SUBSCRIBE*: message sent when we successfully subscribe to a channel. +- *UNSUBSCRIBE*: message sent when we successfully unsubscribe to a channel. +- *PSUBSCRIBE*: message sent when we successfully subscribe to a channel pattern. +- *PUNSUBSCRIBE*: message sent when we successfully unsubscribe to a channel pattern. + +We call messages of *SUBSCRIBE*, *UNSUBSCRIBE*, *PSUBSCRIBE* and *PUNSUBSCRIBE* types as *META MESSAGE*s. + +In order to process these messages, you can set callback functions on `Subscriber`: +- `Subscriber::on_message(MsgCallback)`: set callback function for messages of *MESSAGE* type, and the callback interface is: `void (std::string channel, std::string msg)`. +- `Subscriber::on_pmessage(PatternMsgCallback)`: set the callback function for messages of *PMESSAGE* type, and the callback interface is: `void (std::string pattern, std::string channel, std::string msg)`. +- `Subscriber::on_meta(MetaCallback)`: set callback function for messages of *META MESSAGE* type, and the callback interface is: `void (Subscriber::MsgType type, OptionalString channel, long long num)`. `type` is an enum, it can be one of the following enum: `Subscriber::MsgType::SUBSCRIBE`, `Subscriber::MsgType::UNSUBSCRIBE`, `Subscriber::MsgType::PSUBSCRIBE`, `Subscriber::MsgType::PUNSUBSCRIBE`, `Subscriber::MsgType::MESSAGE`, and `Subscriber::MsgType::PMESSAGE`. If you haven't subscribe/psubscribe to any channel/pattern, and try to unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all channels/patterns, *channel* will be null. So the second parameter of meta callback is of type `OptionalString`. + +All these callback interfaces pass `std::string` by value, and you can take their ownership (i.e. `std::move`) safely. + +#### Consume Messages + +You can call `Subscriber::consume` to consume messages published to channels/patterns that the `Subscriber` has been subscribed. + +`Subscriber::consume` waits for message from the underlying connection. If the `ConnectionOptions::socket_timeout` is reached, and there's no message sent to this connection, `Subscriber::consume` throws a `TimeoutError` exception. If `ConnectionOptions::socket_timeout` is `0ms`, `Subscriber::consume` blocks until it receives a message. + +After receiving the message, `Subscriber::consume` calls the callback function to process the message based on message type. However, if you don't set callback for a specific kind of message, `Subscriber::consume` will ignore the received message, i.e. no callback will be called. + +#### Examples + +The following example is a common pattern for using `Subscriber`: + +```C++ +// Create a Subscriber. +auto sub = redis.subscriber(); + +// Set callback functions. +sub.on_message([](std::string channel, std::string msg) { + // Process message of MESSAGE type. + }); + +sub.on_pmessage([](std::string pattern, std::string channel, std::string msg) { + // Process message of PMESSAGE type. + }); + +sub.on_meta([](Subscriber::MsgType type, OptionalString channel, long long num) { + // Process message of META type. + }); + +// Subscribe to channels and patterns. +sub.subscribe("channel1"); +sub.subscribe({"channel2", "channel3"}); + +sub.psubscribe("pattern1*"); + +// Consume messages in a loop. +while (true) { + try { + sub.consume(); + } catch (const Error &err) { + // Handle exceptions. + } +} +``` + +If `ConnectionOptions::socket_timeout` is set, you might get `TimeoutError` exception before receiving a message: + +```C++ +while (true) { + try { + sub.consume(); + } catch (const TimeoutError &e) { + // Try again. + continue; + } catch (const Error &err) { + // Handle other exceptions. + } +} +``` + +The above examples use lambda as callback. If you're not familiar with lambda, you can also set a free function as callback. Check [this issue](https://github.com/sewenew/redis-plus-plus/issues/16) for detail. + +### Pipeline + +[Pipeline](https://redis.io/topics/pipelining) is used to reduce *RTT* (Round Trip Time), and speed up Redis queries. *redis-plus-plus* supports pipeline with the `Pipeline` class. + +#### Create Pipeline + +You can create a pipeline with `Redis::pipeline` method, which returns a `Pipeline` object. + +```C++ +ConnectionOptions connection_options; +ConnectionPoolOptions pool_options; + +Redis redis(connection_options, pool_options); + +auto pipe = redis.pipeline(); +``` + +When creating a `Pipeline` object, `Redis::pipeline` method creates a new connection to Redis server. This connection is NOT picked from the connection pool, but a newly created connection. This connection has the same `ConnectionOptions` as other connections in the connection pool. `Pipeline` object maintains the new connection, and all piped commands are sent through this connection. + +**NOTE**: Creating a `Pipeline` object is NOT cheap, since it creates a new connection. So you'd better reuse the `Pipeline` object as much as possible. + +#### Send Commands + +You can send Redis commands through the `Pipeline` object. Just like the `Redis` class, `Pipeline` has one or more (overloaded) methods for each Redis command. However, you CANNOT get the replies until you call `Pipeline::exec`. So these methods do NOT return the reply, instead they return the `Pipeline` object itself. And you can chain these methods calls. + +```C++ +pipe.set("key", "val").incr("num").rpush("list", {0, 1, 2}).command("hset", "key", "field", "value"); +``` + +#### Get Replies + +Once you finish sending commands to Redis, you can call `Pipeline::exec` to get replies of these commands. You can also chain `Pipeline::exec` with other commands. + +```C++ +pipe.set("key", "val").incr("num"); +auto replies = pipe.exec(); + +// The same as: +replies = pipe.set("key", "val").incr("num).exec(); +``` + +In fact, these commands won't be sent to Redis, until you call `Pipeline::exec`. So `Pipeline::exec` does 2 work in order: send all piped commands, then get all replies from Redis. + +Also you can call `Pipeline::discard` to discard those piped commands. + +```C++ +pipe.set("key", "val").incr("num"); + +pipe.discard(); +``` + +#### Parse Replies + +`Pipeline::exec` returns a `QueuedReplies` object, which contains replies of all commands that have been sent to Redis. You can use `QueuedReplies::get` method to get and parse the `ith` reply. It has 3 overloads: + +- `template Result get(std::size_t idx)`: Return the `ith` reply as a return value, and you need to specify the return type as tempalte parameter. +- `template void get(std::size_t idx, Output output)`: If the reply is of type *Array Reply*, you can call this method to write the `ith` reply to an output iterator. Normally, compiler will deduce the type of the output iterator, and you don't need to specify the type parameter explicitly. +- `redisReply& get(std::size_t idx)`: If the reply is NOT a fixed type, call this method to get a reference to `redisReply` object. In this case, you need to call `template T reply::parse(redisReply &)` to parse the reply manually. + +Check the [Return Type section](#return-type) for details on the return types of the result. + +```C++ +auto replies = pipe.set("key", "val").incr("num").lrange("list", 0, -1).exec(); + +auto set_cmd_result = replies.get(0); + +auto incr_cmd_result = replies.get(1); + +std::vector list_cmd_result; +replies.get(2, std::back_inserter(list_cmd_result)); +``` + +#### Exception + +If any of `Pipeline`'s method throws an exception, the `Pipeline` object enters an invalid state. You CANNOT use it any more, but only destroy the object, and create a new one. + +#### Thread Safety + +`Pipeline` is NOT thread-safe. If you want to call its member functions in multi-thread environment, you need to synchronize between threads manually. + +### Transaction + +[Transaction](https://redis.io/topics/transactions) is used to make multiple commands runs atomically. + +#### Create Transaction + +You can create a transaction with `Redis::transaction` method, which returns a `Transaction` object. + +```C++ +ConnectionOptions connection_options; +ConnectionPoolOptions pool_options; + +Redis redis(connection_options, pool_options); + +auto tx = redis.transaction(); +``` + +As the `Pipeline` class, `Transaction` maintains a newly created connection to Redis. This connection has the same `ConnectionOptions` as the `Redis` object. + +**NOTE**: Creating a `Transaction` object is NOT cheap, since it creates a new connection. So you'd better reuse the `Transaction` as much as possible. + +Also you don't need to send [MULTI](https://redis.io/commands/multi) command to Redis. `Transaction` will do that for you automatically. + +#### Send Commands + +`Transaction` shares most of implementation with `Pipeline`. It has the same interfaces as `Pipeline`. You can send commands as what you do with `Pipeline` object. + +```C++ +tx.set("key", "val").incr("num").lpush("list", {0, 1, 2}).command("hset", "key", "field", "val"); +``` + +#### Execute Transaction + +When you call `Transaction::exec`, you explicitly ask Redis to execute those queued commands, and return the replies. Otherwise, these commands won't be executed. Also, you can call `Transaction::discard` to discard the execution, i.e. no command will be executed. Both `Transaction::exec` and `Transaction::discard` can be chained with other commands. + +```C++ +auto replies = tx.set("key", "val").incr("num").exec(); + +tx.set("key", "val").incr("num"); + +// Discard the transaction. +tx.discard(); +``` + +#### Parse Replies + +See [Pipeline's Parse Replies section](#parse-replies) for how to parse the replies. + +#### Piped Transaction + +Normally, we always send multiple commnds in a transaction. In order to improve the performance, you can send these commands in a pipeline. You can create a piped transaction by passing `true` as parameter of `Redis::transaction` method. + +```C++ +// Create a piped transaction +auto tx = redis.transaction(true); +``` + +With this piped transaction, all commands are sent to Redis in a pipeline. + +#### Exception + +If any of `Transaction`'s method throws an exception other than `WatchError`, the `Transaction` object enters an invalid state. You CANNOT use it any more, but only destroy the object and create a new one. + +#### Thread Safety + +`Transacation` is NOT thread-safe. If you want to call its member functions in multi-thread environment, you need to synchronize between threads manually. + +#### Watch + +[WATCH is used to provide a check-and-set(CAS) behavior to Redis transactions](https://redis.io/topics/transactions#optimistic-locking-using-check-and-set). + +The `WATCH` command must be sent in the same connection as the transaction. And normally after the `WATCH` command, we also need to send some other commands to get data from Redis before executing the transaction. Take the following check-and-set case as an example: + +``` +WATCH key // watch a key +val = GET key // get value of the key +new_val = val + 1 // incr the value +MULTI // begin the transaction +SET key new_val // set value only if the value is NOT modified by others +EXEC // try to execute the transaction. + // if val has been modified, the transaction won't be executed. +``` + +However, with `Transaction` object, you CANNOT get the result of commands until the whole transaction has been finished. Instead, you need to create a `Redis` object from the `Transaction` object. The created `Redis` object shares the connection with `Transaction` object. With this created `Redis` object, you can send `WATCH` command and any other Redis commands to Redis server, and get the result immediately. + +Let's see how to implement the above example with *redis-plus-plus*: + +```C++ +auto redis = Redis("tcp://127.0.0.1"); + +// Create a transaction. +auto tx = redis.transaction(); + +// Create a Redis object from the Transaction object. Both objects share the same connection. +auto r = tx.redis(); + +// If the watched key has been modified by other clients, the transaction might fail. +// So we need to retry the transaction in a loop. +while (true) { + try { + // Watch a key. + r.watch("key"); + + // Get the old value. + auto val = r.get("key"); + auto num = 0; + if (val) { + num = std::stoi(*val); + } // else use default value, i.e. 0. + + // Incr value. + ++num; + + // Execute the transaction. + auto replies = tx.set("key", std::to_string(num)).exec(); + + // Transaction has been executed successfully. Check the result and break. + + assert(replies.size() == 1 && replies.get(0) == true); + + break; + } catch (const WatchError &err) { + // Key has been modified by other clients, retry. + continue; + } catch (const Error &err) { + // Something bad happens, and the Transaction object is no longer valid. + throw; + } +} +``` + +### Redis Cluster + +*redis-plus-plus* supports [Redis Cluster](https://redis.io/topics/cluster-tutorial). You can use `RedisCluster` class to send commands to Redis Cluster. It has similar interfaces as `Redis` class. + +#### Connection + +`RedisCluster` connects to all master nodes in the cluster. For each master node, it maintains a connection pool. By now, it doesn't connect to slave nodes. + +You can initialize a `RedisCluster` instance with `ConnectionOptions` and `ConnectionPoolOptions`. You only need to set one master node's host & port in `ConnectionOptions`, and `RedisCluster` will get other nodes' info automatically (with the *CLUSTER SLOTS* command). For each master node, it creates a connection pool with the specified `ConnectionPoolOptions`. If `ConnectionPoolOptions` is not specified, `RedisCluster` maintains a single connection to every master node. + +```C++ +// Set a master node's host & port. +ConnectionOptions connection_options; +connection_options.host = "127.0.0.1"; // Required. +connection_options.port = 7000; // Optional. The default port is 6379. +connection_options.password = "auth"; // Optional. No password by default. + +// Automatically get other nodes' info, +// and connect to every master node with a single connection. +RedisCluster cluster1(connection_options); + +ConnectionPoolOptions pool_options; +pool_options.size = 3; + +// For each master node, maintains a connection pool of size 3. +RedisCluster cluster2(connection_options, pool_options); +``` + +You can also specify connection option with an URI. However, in this way, you can only use default `ConnectionPoolOptions`, i.e. pool of size 1, and CANNOT specify password. + +```C++ +// Specify a master node's host & port. +RedisCluster cluster3("tcp://127.0.0.1:7000"); + +// Use default port, i.e. 6379. +RedisCluster cluster4("tcp://127.0.0.1"); +``` + +##### Note + +- `RedisCluster` only works with tcp connection. It CANNOT connect to Unix Domain Socket. If you specify Unix Domain Socket in `ConnectionOptions`, it throws an exception. +- All nodes in the cluster should have the same password. +- Since [Redis Cluster does NOT support multiple databses](https://redis.io/topics/cluster-spec#implemented-subset), `ConnectionOptions::db` is ignored. + +#### Interfaces + +As we mentioned above, `RedisCluster`'s interfaces are similar to `Redis`. It supports most of `Redis`' interfaces, including the [generic command interface](#generic-command-interface) (see `Redis`' [API Reference section](#api-reference) for details), except the following: + +- Not support commands without key as argument, e.g. `PING`, `INFO`. +- Not support Lua script without key parameters. + +Since there's no key parameter, `RedisCluster` has no idea on to which node these commands should be sent. However there're 2 workarounds for this problem: + +- If you want to send these commands to a specific node, you can create a `Redis` object with that node's host and port, and use the `Redis` object to do the work. +- Instead of host and port, you can also call `Redis RedisCluster::redis(const StringView &hash_tag)` to create a `Redis` object with a hash-tag specifying the node. In this case, the returned `Redis` object creates a new connection to Redis server. + +Also you can use the [hash tags](https://redis.io/topics/cluster-spec#keys-hash-tags) to send multiple-key commands. + +See the [example section](#examples-2) for details. + +##### Publish/Subscribe + +You can publish and subscribe messages with `RedisCluster`. The interfaces are exactly the same as `Redis`, i.e. use `RedisCluster::publish` to publish messages, and use `RedisCluster::subscriber` to create a subscriber to consume messages. See [Publish/Subscribe section](#publishsubscribe) for details. + +##### Pipeline and Transaction + +You can also create `Pipeline` and `Transaction` objects with `RedisCluster`, but the interfaces are different from `Redis`. Since all commands in the pipeline and transaction should be sent to a single node in a single connection, we need to tell `RedisCluster` with which node the pipeline or transaction should be created. + +Instead of specifing the node's IP and port, `RedisCluster`'s pipeline and transaction interfaces allow you to specify the node with a *hash tag*. `RedisCluster` will calculate the slot number with the given *hash tag*, and create a pipeline or transaction with the node holding the slot. + +```C++ +Pipeline RedisCluster::pipeline(const StringView &hash_tag); + +Transaction RedisCluster::transaction(const StringView &hash_tag, bool piped = false); +``` + +With the created `Pipeline` or `Transaction` object, you can send commands with keys located on the same node as the given *hash_tag*. See [Examples section](#examples-2) for an example. + +#### Examples + +```C++ +#include + +using namespace sw::redis; + +auto redis_cluster = RedisCluster("tcp://127.0.0.1:7000"); + +redis_cluster.set("key", "value"); +auto val = redis_cluster.get("key"); +if (val) { + std::cout << *val << std::endl; +} + +// With hash-tag. +redis_cluster.set("key{tag}1", "val1"); +redis_cluster.set("key{tag}2", "val2"); +redis_cluster.set("key{tag}3", "val3"); +std::vector hash_tag_res; +redis_cluster.mget({"key{tag}1", "key{tag}2", "key{tag}3"}, + std::back_inserter(hash_tag_res)); + +redis_cluster.lpush("list", {"1", "2", "3"}); +std::vector list; +redis_cluster.lrange("list", 0, -1, std::back_inserter(list)); + +// Pipline. +auto pipe = redis_cluster.pipeline("counter"); +auto replies = pipe.incr("{counter}:1").incr("{counter}:2").exec(); + +// Transaction. +auto tx = redis_cluster.transaction("key"); +replies = tx.incr("key").get("key").exec(); + +// Create a Redis object with hash-tag. +// It connects to the Redis instance that holds the given key, i.e. hash-tag. +auto r = redis_cluster.redis("hash-tag"); + +// And send command without key parameter to the server. +r.command("client", "setname", "connection-name"); +``` + +**NOTE**: When you use `RedisCluster::redis(const StringView &hash_tag)` to create a `Redis` object, instead of picking a connection from the underlying connection pool, it creates a new connection to the corresponding Redis server. So this is NOT a cheap operation, and you should try to reuse this newly created `Redis` object as much as possible. + +```C++ +// This is BAD! It's very inefficient. +// NEVER DO IT!!! +// After sending PING command, the newly created Redis object will be destroied. +cluster.redis("key").ping(); + +// Then it creates a connection to Redis, and closes the connection after sending the command. +cluster.redis("key").command("client", "setname", "hello"); + +// Instead you should reuse the Redis object. +// This is GOOD! +auto redis = cluster.redis("key"); + +redis.ping(); +redis.command("client", "setname", "hello"); +``` + +#### Details + +`RedisCluster` maintains the newest slot-node mapping, and sends command directly to the right node. Normally it works as fast as `Redis`. If the cluster reshards, `RedisCluster` will follow the redirection, and it will finally update the slot-node mapping. It can correctly handle the following resharding cases: + +- Data migration between exist nodes. +- Add new node to the cluster. +- Remove node from the cluster. + +`redis-plus-plus` is able to handle both [MOVED](https://redis.io/topics/cluster-spec#moved-redirection) and [ASK](https://redis.io/topics/cluster-spec#ask-redirection) redirections, so it's a complete Redis Cluster client. + +If master is down, the cluster will promote one of its replicas to be the new master. *redis-plus-plus* can also handle this case: + +- When the master is down, *redis-plus-plus* losts connection to it. In this case, if you try to send commands to this master, *redis-plus-plus* will try to update slot-node mapping from other nodes. If the mapping remains unchanged, i.e. new master hasn't been elected yet, it fails to send command to Redis Cluster and throws exception. +- When the new master has been elected, the slot-node mapping will be updated by the cluster. In this case, if you send commands to the cluster, *redis-plus-plus* can get an update-to-date mapping, and sends commands to the new master. + +### Redis Sentinel + +[Redis Sentinel provides high availability for Redis](https://redis.io/topics/sentinel). If Redis master is down, Redis Sentinels will elect a new master from slaves, i.e. failover. Besides, Redis Sentinel can also act like a configuration provider for clients, and clients can query master or slave address from Redis Sentinel. So that if a failover occurs, clients can ask the new master address from Redis Sentinel. + +*redis-plus-plus* supports getting Redis master or slave's IP and port from Redis Sentinel. In order to use this feature, you only need to initialize `Redis` object with Redis Sentinel info, which is composed with 3 parts: `std::shared_ptr`, master name and role (master or slave). + +Before using Redis Sentinel with *redis-plus-plus*, ensure that you have read Redis Sentinel's [doc](https://redis.io/topics/sentinel). + +#### Sentinel + +You can create a `std::shared_ptr` object with `SentinelOptions`. + +```C++ +SentinelOptions sentinel_opts; +sentinel_opts.nodes = {{"127.0.0.1", 9000}, + {"127.0.0.1", 9001}, + {"127.0.0.1", 9002}}; // Required. List of Redis Sentinel nodes. + +// Optional. Timeout before we successfully connect to Redis Sentinel. +// By default, the timeout is 100ms. +sentinel_opts.connect_timeout = std::chrono::milliseconds(200); + +// Optional. Timeout before we successfully send request to or receive response from Redis Sentinel. +// By default, the timeout is 100ms. +sentinel_opts.socket_timeout = std::chrono::milliseconds(200); + +auto sentinel = std::make_shared(sentinel_opts); +``` + +`SentinelOptions::connect_timeout` and `SentinelOptions::socket_timeout` CANNOT be 0ms, i.e. no timeout and block forever. Otherwise, *redis-plus-plus* will throw an exception. + +See [SentinelOptions](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/sentinel.h#L33) for more options. + +#### Role + +Besides `std::shared_ptr` and master name, you also need to specify a role. There are two roles: `Role::MASTER`, and `Role::SLAVE`. + +With `Role::MASTER`, *redis-plus-plus* will always connect to current master instance, even if a failover occurs. Each time when *redis-plus-plus* needs to create a new connection to master, or a connection is broken, and it needs to reconnect to master, *redis-plus-plus* will ask master address from Redis Sentinel, and connects to current master. If a failover occurs, *redis-plus-plus* can automatically get the address of the new master, and refresh all connections in the underlying connection pool. + +Similarly, with `Role::SLAVE`, *redis-plus-plus* will always connect to a slave instance. A master might have several slaves, *redis-plus-plus* will randomly pick one, and connect to it, i.e. all connections in the underlying connection pool, connect to the same slave instance. If the connection is broken, while this slave instance is still an alive slave, *redis-plus-plus* will reconnect to this slave. However, if this slave instance is down, or it has been promoted to be the master, *redis-plus-plus* will randomly connect to another slave. If there's no slave alive, it throws an exception. + +#### Create Redis With Sentinel + +When creating a `Redis` object with sentinel, besides the sentinel info, you should also provide `ConnectionOptions` and `ConnectionPoolOptions`. These two options are used to connect to Redis instance. `ConnectionPoolOptions` is optional, if not specified, it creates a single connection the instance. + +```C++ +ConnectionOptions connection_opts; +connection_opts.password = "auth"; // Optional. No password by default. +connection_opts.connect_timeout = std::chrono::milliseconds(100); // Required. +connection_opts.socket_timeout = std::chrono::milliseconds(100); // Required. + +ConnectionPoolOptions pool_opts; +pool_opts.size = 3; // Optional. The default size is 1. + +auto redis = Redis(sentinel, "master_name", Role::MASTER, connection_opts, pool_opts); +``` + +You might have noticed that we didn't specify the `host` and `port` fields for `ConnectionOptions`. Because, `Redis` will get these info from Redis Sentinel. Also, in this case, `ConnectionOptions::connect_timeout` and `ConnectionOptions::socket_timeout` CANNOT be 0ms, otherwise, it throws an exception. So you always need to specify these two timeouts manually. + +After creating the `Redis` object with sentinel, you can send commands with it, just like an ordinary `Redis` object. + +If you want to write to master, and scale read with slaves. You can use the following pattern: + +```C++ +auto sentinel = std::make_shared(sentinel_opts); + +auto master = Redis(sentinel, "master_name", Role::MASTER, connection_opts, pool_opts); + +auto slave = Redis(sentinel, "master_name", Role::SLAVE, connection_opts, pool_opts); + +// Write to master. +master.set("key", "value"); + +// Read from slave. +slave.get("key"); +``` + +### Redis Stream + +Since Redis 5.0, it introduces a new data type: *Redis Stream*. *redis-plus-plus* has built-in methods for all stream commands except the *XINFO* command (of course, you can use the [Generic Command Interface](#generic-command-interface) to send *XINFO* command). + +However, the replies of some streams commands, i.e. *XPENDING*, *XREAD*, are complex. So I'll give some examples to show you how to work with these built-in methods. + +#### Examples + +```C++ +auto redis = Redis("tcp://127.0.0.1"); + +using Attrs = std::vector>; + +// You can also use std::unordered_map, if you don't care the order of attributes: +// using Attrs = std::unordered_map; + +Attrs attrs = { {"f1", "v1"}, {"f2", "v2"} }; + +// Add an item into the stream. This method returns the auto generated id. +auto id = redis.xadd("key", "*", attrs.begin(), attrs.end()); + +// Each item is assigned with an id: pair. +using Item = std::pair; +using ItemStream = std::vector; + +// If you don't care the order of items in the stream, you can also use unordered_map: +// using ItemStream = std::unordered_map; + +// Read items from a stream, and return at most 10 items. +// You need to specify a key and an id (timestamp + offset). +std::unordered_map result; +redis.xread("key", id, 10, std::inserter(result, result.end())); + +// Read from multiple streams. For each stream, you need to specify a key and an id. +std::unordered_map keys = { {"key", id}, {"another-key", "0-0"} }; +redis.xread(keys.begin(), keys.end(), 10, std::inserter(result, result.end())); + +// Block for at most 1 second if currently there's no data in the stream. +redis.xread("key", id, std::chrono::seconds(1), 10, std::inserter(result, result.end())); + +// Block for multiple streams. +redis.xread(keys.begin(), keys.end(), std::chrono::seconds(1), 10, std::inserter(result, result.end())); + +// Read items in a range: +ItemStream item_stream; +redis.xrange("key", "-", "+", std::back_inserter(item_stream)); + +// Trim the stream to a given number of items. After the operation, the stream length is NOT exactly +// 10. Instead, it might be much larger than 10. +// `XTRIM key MAXLEN 10` +redis.xtrim("key", 10); + +// In order to trim the stream to exactly 10 items, specify the third argument, i.e. approx, as false. +// `XTRIM key MAXLEN ~ 10` +redis.xtrim("key", 10, false); + +// Delete an item from the stream. +redis.xdel("key", id); + +// Create a consumer group. +redis.xgroup_create("key", "group", "$"); + +// If the stream doesn't exist, you can set the fourth argument, i.e. MKSTREAM, to be true. +// redis.xgroup_create("key", "group", "$", true); + +id = redis.xadd("key", "*", attrs.begin(), attrs.end()); + +// Read item by a consumer of a consumer group. +redis.xreadgroup("group", "consumer", "key", ">", 1, std::inserter(result, result.end())); + +using PendingItem = std::tuple; +std::vector pending_items; + +// Get pending items of a speicified consumer. +redis.xpending("key", "group", "-", "+", 1, "consumer", std::back_inserter(pending_items)); + +redis.xack("key", "group", id); + +redis.xgroup_delconsumer("key", "group", "consumer"); +redis.xgroup_destroy("key", "group"); +``` + +If you have any problem on sending stream commands to Redis, please feel free to let me know. + +## Author + +*redis-plus-plus* is written by sewenew, who is also active on [StackOverflow](https://stackoverflow.com/users/5384363/for-stack). diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command.h new file mode 100644 index 000000000..3a4b24c5e --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command.h @@ -0,0 +1,2233 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_H + +#include +#include +#include +#include +#include "connection.h" +#include "command_options.h" +#include "command_args.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace cmd { + +// CONNECTION command. +inline void auth(Connection &connection, const StringView &password) { + connection.send("AUTH %b", password.data(), password.size()); +} + +inline void echo(Connection &connection, const StringView &msg) { + connection.send("ECHO %b", msg.data(), msg.size()); +} + +inline void ping(Connection &connection) { + connection.send("PING"); +} + +inline void quit(Connection &connection) { + connection.send("QUIT"); +} + +inline void ping(Connection &connection, const StringView &msg) { + // If *msg* is empty, Redis returns am empty reply of REDIS_REPLY_STRING type. + connection.send("PING %b", msg.data(), msg.size()); +} + +inline void select(Connection &connection, long long idx) { + connection.send("SELECT %lld", idx); +} + +inline void swapdb(Connection &connection, long long idx1, long long idx2) { + connection.send("SWAPDB %lld %lld", idx1, idx2); +} + +// SERVER commands. + +inline void bgrewriteaof(Connection &connection) { + connection.send("BGREWRITEAOF"); +} + +inline void bgsave(Connection &connection) { + connection.send("BGSAVE"); +} + +inline void dbsize(Connection &connection) { + connection.send("DBSIZE"); +} + +inline void flushall(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHALL ASYNC"); + } else { + connection.send("FLUSHALL"); + } +} + +inline void flushdb(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHDB ASYNC"); + } else { + connection.send("FLUSHDB"); + } +} + +inline void info(Connection &connection) { + connection.send("INFO"); +} + +inline void info(Connection &connection, const StringView §ion) { + connection.send("INFO %b", section.data(), section.size()); +} + +inline void lastsave(Connection &connection) { + connection.send("LASTSAVE"); +} + +inline void save(Connection &connection) { + connection.send("SAVE"); +} + +// KEY commands. + +inline void del(Connection &connection, const StringView &key) { + connection.send("DEL %b", key.data(), key.size()); +} + +template +inline void del_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "DEL" << std::make_pair(first, last); + + connection.send(args); +} + +inline void dump(Connection &connection, const StringView &key) { + connection.send("DUMP %b", key.data(), key.size()); +} + +inline void exists(Connection &connection, const StringView &key) { + connection.send("EXISTS %b", key.data(), key.size()); +} + +template +inline void exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void expire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("EXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void expireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("EXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void keys(Connection &connection, const StringView &pattern) { + connection.send("KEYS %b", pattern.data(), pattern.size()); +} + +inline void move(Connection &connection, const StringView &key, long long db) { + connection.send("MOVE %b %lld", + key.data(), key.size(), + db); +} + +inline void persist(Connection &connection, const StringView &key) { + connection.send("PERSIST %b", key.data(), key.size()); +} + +inline void pexpire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("PEXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void pexpireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("PEXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void pttl(Connection &connection, const StringView &key) { + connection.send("PTTL %b", key.data(), key.size()); +} + +inline void randomkey(Connection &connection) { + connection.send("RANDOMKEY"); +} + +inline void rename(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAME %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +inline void renamenx(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAMENX %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +void restore(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + bool replace); + +inline void scan(Connection &connection, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SCAN %lld MATCH %b COUNT %lld", + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void touch(Connection &connection, const StringView &key) { + connection.send("TOUCH %b", key.data(), key.size()); +} + +template +inline void touch_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "TOUCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void ttl(Connection &connection, const StringView &key) { + connection.send("TTL %b", key.data(), key.size()); +} + +inline void type(Connection &connection, const StringView &key) { + connection.send("TYPE %b", key.data(), key.size()); +} + +inline void unlink(Connection &connection, const StringView &key) { + connection.send("UNLINK %b", key.data(), key.size()); +} + +template +inline void unlink_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "UNLINK" << std::make_pair(first, last); + + connection.send(args); +} + +inline void wait(Connection &connection, long long numslave, long long timeout) { + connection.send("WAIT %lld %lld", numslave, timeout); +} + +// STRING commands. + +inline void append(Connection &connection, const StringView &key, const StringView &str) { + connection.send("APPEND %b %b", + key.data(), key.size(), + str.data(), str.size()); +} + +inline void bitcount(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("BITCOUNT %b %lld %lld", + key.data(), key.size(), + start, end); +} + +void bitop(Connection &connection, + BitOp op, + const StringView &destination, + const StringView &key); + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last); + +inline void bitpos(Connection &connection, + const StringView &key, + long long bit, + long long start, + long long end) { + connection.send("BITPOS %b %lld %lld %lld", + key.data(), key.size(), + bit, + start, + end); +} + +inline void decr(Connection &connection, const StringView &key) { + connection.send("DECR %b", key.data(), key.size()); +} + +inline void decrby(Connection &connection, const StringView &key, long long decrement) { + connection.send("DECRBY %b %lld", + key.data(), key.size(), + decrement); +} + +inline void get(Connection &connection, const StringView &key) { + connection.send("GET %b", + key.data(), key.size()); +} + +inline void getbit(Connection &connection, const StringView &key, long long offset) { + connection.send("GETBIT %b %lld", + key.data(), key.size(), + offset); +} + +inline void getrange(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("GETRANGE %b %lld %lld", + key.data(), key.size(), + start, + end); +} + +inline void getset(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("GETSET %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void incr(Connection &connection, const StringView &key) { + connection.send("INCR %b", key.data(), key.size()); +} + +inline void incrby(Connection &connection, const StringView &key, long long increment) { + connection.send("INCRBY %b %lld", + key.data(), key.size(), + increment); +} + +inline void incrbyfloat(Connection &connection, const StringView &key, double increment) { + connection.send("INCRBYFLOAT %b %f", + key.data(), key.size(), + increment); +} + +template +inline void mget(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MGET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void mset(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void msetnx(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSETNX" << std::make_pair(first, last); + + connection.send(args); +} + +inline void psetex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("PSETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +void set(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + UpdateType type); + +inline void setex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("SETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +inline void setnx(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("SETNX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void setrange(Connection &connection, + const StringView &key, + long long offset, + const StringView &val) { + connection.send("SETRANGE %b %lld %b", + key.data(), key.size(), + offset, + val.data(), val.size()); +} + +inline void strlen(Connection &connection, const StringView &key) { + connection.send("STRLEN %b", key.data(), key.size()); +} + +// LIST commands. + +inline void blpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BLPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void blpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BLPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BRPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void brpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BRPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpoplpush(Connection &connection, + const StringView &source, + const StringView &destination, + long long timeout) { + connection.send("BRPOPLPUSH %b %b %lld", + source.data(), source.size(), + destination.data(), destination.size(), + timeout); +} + +inline void lindex(Connection &connection, const StringView &key, long long index) { + connection.send("LINDEX %b %lld", + key.data(), key.size(), + index); +} + +void linsert(Connection &connection, + const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + +inline void llen(Connection &connection, + const StringView &key) { + connection.send("LLEN %b", key.data(), key.size()); +} + +inline void lpop(Connection &connection, const StringView &key) { + connection.send("LPOP %b", + key.data(), key.size()); +} + +inline void lpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void lpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "LPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void lpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void lrange(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void lrem(Connection &connection, + const StringView &key, + long long count, + const StringView &val) { + connection.send("LREM %b %lld %b", + key.data(), key.size(), + count, + val.data(), val.size()); +} + +inline void lset(Connection &connection, + const StringView &key, + long long index, + const StringView &val) { + connection.send("LSET %b %lld %b", + key.data(), key.size(), + index, + val.data(), val.size()); +} + +inline void ltrim(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LTRIM %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void rpop(Connection &connection, const StringView &key) { + connection.send("RPOP %b", key.data(), key.size()); +} + +inline void rpoplpush(Connection &connection, + const StringView &source, + const StringView &destination) { + connection.send("RPOPLPUSH %b %b", + source.data(), source.size(), + destination.data(), destination.size()); +} + +inline void rpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void rpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "RPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void rpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +// HASH commands. + +inline void hdel(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HDEL %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +template +inline void hdel_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hexists(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HEXISTS %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hget(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HGET %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hgetall(Connection &connection, const StringView &key) { + connection.send("HGETALL %b", key.data(), key.size()); +} + +inline void hincrby(Connection &connection, + const StringView &key, + const StringView &field, + long long increment) { + connection.send("HINCRBY %b %b %lld", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hincrbyfloat(Connection &connection, + const StringView &key, + const StringView &field, + double increment) { + connection.send("HINCRBYFLOAT %b %b %f", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hkeys(Connection &connection, const StringView &key) { + connection.send("HKEYS %b", key.data(), key.size()); +} + +inline void hlen(Connection &connection, const StringView &key) { + connection.send("HLEN %b", key.data(), key.size()); +} + +template +inline void hmget(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMGET" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void hmset(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMSET" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("HSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void hset(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSET %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +inline void hsetnx(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSETNX %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +inline void hstrlen(Connection &connection, + const StringView &key, + const StringView &field) { + connection.send("HSTRLEN %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hvals(Connection &connection, const StringView &key) { + connection.send("HVALS %b", key.data(), key.size()); +} + +// SET commands + +inline void sadd(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SADD %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void sadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void scard(Connection &connection, const StringView &key) { + connection.send("SCARD %b", key.data(), key.size()); +} + +template +inline void sdiff(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFF" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sdiffstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SDIFFSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sdiffstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFFSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void sinter(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTER" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sinterstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SINTERSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTERSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +inline void sismember(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SISMEMBER %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void smembers(Connection &connection, const StringView &key) { + connection.send("SMEMBERS %b", key.data(), key.size()); +} + +inline void smove(Connection &connection, + const StringView &source, + const StringView &destination, + const StringView &member) { + connection.send("SMOVE %b %b %b", + source.data(), source.size(), + destination.data(), destination.size(), + member.data(), member.size()); +} + +inline void spop(Connection &connection, const StringView &key) { + connection.send("SPOP %b", key.data(), key.size()); +} + +inline void spop_range(Connection &connection, const StringView &key, long long count) { + connection.send("SPOP %b %lld", + key.data(), key.size(), + count); +} + +inline void srandmember(Connection &connection, const StringView &key) { + connection.send("SRANDMEMBER %b", key.data(), key.size()); +} + +inline void srandmember_range(Connection &connection, + const StringView &key, + long long count) { + connection.send("SRANDMEMBER %b %lld", + key.data(), key.size(), + count); +} + +inline void srem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void srem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void sscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +template +inline void sunion(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNION" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sunionstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SUNIONSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNIONSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// Sorted Set commands. + +inline void bzpopmax(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMAX %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmax_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMAX" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void bzpopmin(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMIN %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmin_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMIN" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed); + +inline void zadd(Connection &connection, + const StringView &key, + const StringView &member, + double score, + UpdateType type, + bool changed) { + auto tmp = {std::make_pair(member, score)}; + + zadd_range(connection, key, tmp.begin(), tmp.end(), type, changed); +} + +inline void zcard(Connection &connection, const StringView &key) { + connection.send("ZCARD %b", key.data(), key.size()); +} + +template +inline void zcount(Connection &connection, + const StringView &key, + const Interval &interval) { + connection.send("ZCOUNT %b %s %s", + key.data(), key.size(), + interval.min().c_str(), + interval.max().c_str()); +} + +inline void zincrby(Connection &connection, + const StringView &key, + double increment, + const StringView &member) { + connection.send("ZINCRBY %b %f %b", + key.data(), key.size(), + increment, + member.data(), member.size()); +} + +inline void zinterstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZINTERSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +template +inline void zlexcount(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZLEXCOUNT %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zpopmax(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMAX %b %lld", + key.data(), key.size(), + count); +} + +inline void zpopmin(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMIN %b %lld", + key.data(), key.size(), + count); +} + +inline void zrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); +} + +template +void zrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } else { + connection.send("ZRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } +} + +inline void zrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zrem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void zrem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "ZREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void zremrangebylex(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYLEX %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zremrangebyrank(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("zremrangebyrank %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +template +inline void zremrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYSCORE %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zrevrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZREVRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZREVRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrevrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREVRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); +} + +template +void zrevrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZREVRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } else { + connection.send("ZREVRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } +} + +inline void zrevrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREVRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("ZSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void zscore(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZSCORE %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zunionstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZUNIONSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +// HYPERLOGLOG commands. + +inline void pfadd(Connection &connection, + const StringView &key, + const StringView &element) { + connection.send("PFADD %b %b", + key.data(), key.size(), + element.data(), element.size()); +} + +template +inline void pfadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfcount(Connection &connection, const StringView &key) { + connection.send("PFCOUNT %b", key.data(), key.size()); +} + +template +inline void pfcount_range(Connection &connection, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFCOUNT" << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfmerge(Connection &connection, const StringView &destination, const StringView &key) { + connection.send("PFMERGE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void pfmerge_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFMERGE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// GEO commands. + +inline void geoadd(Connection &connection, + const StringView &key, + const std::tuple &member) { + const auto &mem = std::get<0>(member); + + connection.send("GEOADD %b %f %f %b", + key.data(), key.size(), + std::get<1>(member), + std::get<2>(member), + mem.data(), mem.size()); +} + +template +inline void geoadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOADD" << key; + + while (first != last) { + const auto &member = *first; + args << std::get<1>(member) << std::get<2>(member) << std::get<0>(member); + ++first; + } + + connection.send(args); +} + +void geodist(Connection &connection, + const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit); + +template +inline void geohash_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOHASH" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void geopos_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOPOS" << key << std::make_pair(first, last); + + connection.send(args); +} + +void georadius(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadius_store(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void georadiusbymember(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadiusbymember_store(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +// SCRIPTING commands. + +inline void eval(Connection &connection, + const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + CmdArgs cmd_args; + + cmd_args << "EVAL" << script << keys.size() + << std::make_pair(keys.begin(), keys.end()) + << std::make_pair(args.begin(), args.end()); + + connection.send(cmd_args); +} + +inline void evalsha(Connection &connection, + const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + CmdArgs cmd_args; + + cmd_args << "EVALSHA" << script << keys.size() + << std::make_pair(keys.begin(), keys.end()) + << std::make_pair(args.begin(), args.end()); + + connection.send(cmd_args); +} + +inline void script_exists(Connection &connection, const StringView &sha) { + connection.send("SCRIPT EXISTS %b", sha.data(), sha.size()); +} + +template +inline void script_exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SCRIPT" << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void script_flush(Connection &connection) { + connection.send("SCRIPT FLUSH"); +} + +inline void script_kill(Connection &connection) { + connection.send("SCRIPT KILL"); +} + +inline void script_load(Connection &connection, const StringView &script) { + connection.send("SCRIPT LOAD %b", script.data(), script.size()); +} + +// PUBSUB commands. + +inline void psubscribe(Connection &connection, const StringView &pattern) { + connection.send("PSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void psubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void publish(Connection &connection, + const StringView &channel, + const StringView &message) { + connection.send("PUBLISH %b %b", + channel.data(), channel.size(), + message.data(), message.size()); +} + +inline void punsubscribe(Connection &connection) { + connection.send("PUNSUBSCRIBE"); +} + +inline void punsubscribe(Connection &connection, const StringView &pattern) { + connection.send("PUNSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void punsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PUNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PUNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void subscribe(Connection &connection, const StringView &channel) { + connection.send("SUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void subscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("SUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "SUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void unsubscribe(Connection &connection) { + connection.send("UNSUBSCRIBE"); +} + +inline void unsubscribe(Connection &connection, const StringView &channel) { + connection.send("UNSUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void unsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "UNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +// Transaction commands. + +inline void discard(Connection &connection) { + connection.send("DISCARD"); +} + +inline void exec(Connection &connection) { + connection.send("EXEC"); +} + +inline void multi(Connection &connection) { + connection.send("MULTI"); +} + +inline void unwatch(Connection &connection, const StringView &key) { + connection.send("UNWATCH %b", key.data(), key.size()); +} + +template +inline void unwatch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNWATCH: no key specified"); + } + + CmdArgs args; + args << "UNWATCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void watch(Connection &connection, const StringView &key) { + connection.send("WATCH %b", key.data(), key.size()); +} + +template +inline void watch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("WATCH: no key specified"); + } + + CmdArgs args; + args << "WATCH" << std::make_pair(first, last); + + connection.send(args); +} + +// Stream commands. + +inline void xack(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XACK %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +template +void xack_range(Connection &connection, + const StringView &key, + const StringView &group, + Input first, + Input last) { + CmdArgs args; + args << "XACK" << key << group << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last) { + CmdArgs args; + args << "XADD" << key << id << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_maxlen_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + CmdArgs args; + args << "XADD" << key << "MAXLEN"; + + if (approx) { + args << "~"; + } + + args << count << id << std::make_pair(first, last); + + connection.send(args); +} + +inline void xclaim(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + const StringView &id) { + connection.send("XCLAIM %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size(), + min_idle_time, + id.data(), id.size()); +} + +template +void xclaim_range(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + Input first, + Input last) { + CmdArgs args; + args << "XCLAIM" << key << group << consumer << min_idle_time << std::make_pair(first, last); + + connection.send(args); +} + +inline void xdel(Connection &connection, const StringView &key, const StringView &id) { + connection.send("XDEL %b %b", key.data(), key.size(), id.data(), id.size()); +} + +template +void xdel_range(Connection &connection, const StringView &key, Input first, Input last) { + CmdArgs args; + args << "XDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void xgroup_create(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream) { + CmdArgs args; + args << "XGROUP" << "CREATE" << key << group << id; + + if (mkstream) { + args << "MKSTREAM"; + } + + connection.send(args); +} + +inline void xgroup_setid(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XGROUP SETID %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +inline void xgroup_destroy(Connection &connection, + const StringView &key, + const StringView &group) { + connection.send("XGROUP DESTROY %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xgroup_delconsumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer) { + connection.send("XGROUP DELCONSUMER %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size()); +} + +inline void xlen(Connection &connection, const StringView &key) { + connection.send("XLEN %b", key.data(), key.size()); +} + +inline void xpending(Connection &connection, const StringView &key, const StringView &group) { + connection.send("XPENDING %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xpending_detail(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XPENDING %b %b %b %b %lld", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xpending_per_consumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + connection.send("XPENDING %b %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count, + consumer.data(), consumer.size()); +} + +inline void xrange(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end) { + connection.send("XRANGE %b %b %b", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size()); +} + +inline void xrange_count(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xread(Connection &connection, + const StringView &key, + const StringView &id, + long long count) { + connection.send("XREAD COUNT %lld STREAMS %b %b", + count, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_range(Connection &connection, Input first, Input last, long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xread_block(Connection &connection, + const StringView &key, + const StringView &id, + long long timeout, + long long count) { + connection.send("XREAD COUNT %lld BLOCK %lld STREAMS %b %b", + count, + timeout, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_block_range(Connection &connection, + Input first, + Input last, + long long timeout, + long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "BLOCK" << timeout << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup_block(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_block_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xrevrange(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start) { + connection.send("XREVRANGE %b %b %b", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size()); +} + +inline void xrevrange_count(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + connection.send("XREVRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size(), + count); +} + +void xtrim(Connection &connection, const StringView &key, long long count, bool approx); + +namespace detail { + +void set_bitop(CmdArgs &args, BitOp op); + +void set_update_type(CmdArgs &args, UpdateType type); + +void set_aggregation_type(CmdArgs &args, Aggregation type); + +template +void zinterstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zinterstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +void set_geo_unit(CmdArgs &args, GeoUnit unit); + +void set_georadius_store_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void set_georadius_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +} + +} + +} + +} + +namespace sw { + +namespace redis { + +namespace cmd { + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + + detail::set_bitop(args, op); + + args << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + assert(first != last); + + CmdArgs args; + + args << "ZADD" << key; + + detail::set_update_type(args, type); + + if (changed) { + args << "CH"; + } + + while (first != last) { + // Swap the pair to pair. + args << first->second << first->first; + ++first; + } + + connection.send(args); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zinterstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zunionstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_args.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_args.h new file mode 100644 index 000000000..0beb71e5c --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_args.h @@ -0,0 +1,180 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H + +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +class CmdArgs { +public: + template + CmdArgs& append(Arg &&arg); + + template + CmdArgs& append(Arg &&arg, Args &&...args); + + // All overloads of operator<< are for internal use only. + CmdArgs& operator<<(const StringView &arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& operator<<(T &&arg); + + template + CmdArgs& operator<<(const std::pair &range); + + template + auto operator<<(const std::tuple &) -> + typename std::enable_if::type { + return *this; + } + + template + auto operator<<(const std::tuple &arg) -> + typename std::enable_if::type; + + const char** argv() { + return _argv.data(); + } + + const std::size_t* argv_len() { + return _argv_len.data(); + } + + std::size_t size() const { + return _argv.size(); + } + +private: + // Deep copy. + CmdArgs& _append(std::string arg); + + // Shallow copy. + CmdArgs& _append(const StringView &arg); + + // Shallow copy. + CmdArgs& _append(const char *arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& _append(T &&arg) { + return operator<<(std::forward(arg)); + } + + template + CmdArgs& _append(std::true_type, const std::pair &range); + + template + CmdArgs& _append(std::false_type, const std::pair &range); + + std::vector _argv; + std::vector _argv_len; + + std::list _args; +}; + +template +inline CmdArgs& CmdArgs::append(Arg &&arg) { + return _append(std::forward(arg)); +} + +template +inline CmdArgs& CmdArgs::append(Arg &&arg, Args &&...args) { + _append(std::forward(arg)); + + return append(std::forward(args)...); +} + +inline CmdArgs& CmdArgs::operator<<(const StringView &arg) { + _argv.push_back(arg.data()); + _argv_len.push_back(arg.size()); + + return *this; +} + +template +inline CmdArgs& CmdArgs::operator<<(const std::pair &range) { + return _append(IsKvPair())>::type>(), range); +} + +template ::type>::value, + int>::type> +inline CmdArgs& CmdArgs::operator<<(T &&arg) { + return _append(std::to_string(std::forward(arg))); +} + +template +auto CmdArgs::operator<<(const std::tuple &arg) -> + typename std::enable_if::type { + operator<<(std::get(arg)); + + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(std::string arg) { + _args.push_back(std::move(arg)); + return operator<<(_args.back()); +} + +inline CmdArgs& CmdArgs::_append(const StringView &arg) { + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(const char *arg) { + return operator<<(arg); +} + +template +CmdArgs& CmdArgs::_append(std::false_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << *first; + ++first; + } + + return *this; +} + +template +CmdArgs& CmdArgs::_append(std::true_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << first->first << first->second; + ++first; + } + + return *this; +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_options.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_options.h new file mode 100644 index 000000000..ca766c086 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_options.h @@ -0,0 +1,211 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H + +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +enum class UpdateType { + EXIST, + NOT_EXIST, + ALWAYS +}; + +enum class InsertPosition { + BEFORE, + AFTER +}; + +enum class BoundType { + CLOSED, + OPEN, + LEFT_OPEN, + RIGHT_OPEN +}; + +// (-inf, +inf) +template +class UnboundedInterval; + +// [min, max], (min, max), (min, max], [min, max) +template +class BoundedInterval; + +// [min, +inf), (min, +inf) +template +class LeftBoundedInterval; + +// (-inf, max], (-inf, max) +template +class RightBoundedInterval; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(double min, double max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(double min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(double max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(const std::string &min, const std::string &max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(const std::string &min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(const std::string &max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +struct LimitOptions { + long long offset = 0; + long long count = -1; +}; + +enum class Aggregation { + SUM, + MIN, + MAX +}; + +enum class BitOp { + AND, + OR, + XOR, + NOT +}; + +enum class GeoUnit { + M, + KM, + MI, + FT +}; + +template +struct WithCoord : TupleWithType, T> {}; + +template +struct WithDist : TupleWithType {}; + +template +struct WithHash : TupleWithType {}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection.h new file mode 100644 index 000000000..5ad419225 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection.h @@ -0,0 +1,194 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_H + +#include +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "reply.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +enum class ConnectionType { + TCP = 0, + UNIX +}; + +struct ConnectionOptions { +public: + ConnectionOptions() = default; + + explicit ConnectionOptions(const std::string &uri); + + ConnectionOptions(const ConnectionOptions &) = default; + ConnectionOptions& operator=(const ConnectionOptions &) = default; + + ConnectionOptions(ConnectionOptions &&) = default; + ConnectionOptions& operator=(ConnectionOptions &&) = default; + + ~ConnectionOptions() = default; + + ConnectionType type = ConnectionType::TCP; + + std::string host; + + int port = 6379; + + std::string path; + + std::string password; + + int db = 0; + + bool keep_alive = false; + + std::chrono::milliseconds connect_timeout{0}; + + std::chrono::milliseconds socket_timeout{0}; + +private: + ConnectionOptions _parse_options(const std::string &uri) const; + + ConnectionOptions _parse_tcp_options(const std::string &path) const; + + ConnectionOptions _parse_unix_options(const std::string &path) const; + + auto _split_string(const std::string &str, const std::string &delimiter) const -> + std::pair; +}; + +class CmdArgs; + +class Connection { +public: + explicit Connection(const ConnectionOptions &opts); + + Connection(const Connection &) = delete; + Connection& operator=(const Connection &) = delete; + + Connection(Connection &&) = default; + Connection& operator=(Connection &&) = default; + + ~Connection() = default; + + // Check if the connection is broken. Client needs to do this check + // before sending some command to the connection. If it's broken, + // client needs to reconnect it. + bool broken() const noexcept { + return _ctx->err != REDIS_OK; + } + + void reset() noexcept { + _ctx->err = 0; + } + + void reconnect(); + + auto last_active() const + -> std::chrono::time_point { + return _last_active; + } + + template + void send(const char *format, Args &&...args); + + void send(int argc, const char **argv, const std::size_t *argv_len); + + void send(CmdArgs &args); + + ReplyUPtr recv(); + + const ConnectionOptions& options() const { + return _opts; + } + + friend void swap(Connection &lhs, Connection &rhs) noexcept; + +private: + class Connector; + + struct ContextDeleter { + void operator()(redisContext *context) const { + if (context != nullptr) { + redisFree(context); + } + }; + }; + + using ContextUPtr = std::unique_ptr; + + void _set_options(); + + void _auth(); + + void _select_db(); + + redisContext* _context(); + + ContextUPtr _ctx; + + // The time that the connection is created or the time that + // the connection is used, i.e. *context()* is called. + std::chrono::time_point _last_active{}; + + ConnectionOptions _opts; +}; + +using ConnectionSPtr = std::shared_ptr; + +enum class Role { + MASTER, + SLAVE +}; + +// Inline implementaions. + +template +inline void Connection::send(const char *format, Args &&...args) { + auto ctx = _context(); + + assert(ctx != nullptr); + + if (redisAppendCommand(ctx, + format, + std::forward(args)...) != REDIS_OK) { + throw_error(*ctx, "Failed to send command"); + } + + assert(!broken()); +} + +inline redisContext* Connection::_context() { + _last_active = std::chrono::steady_clock::now(); + + return _ctx.get(); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection_pool.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection_pool.h new file mode 100644 index 000000000..6f2663ad7 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection_pool.h @@ -0,0 +1,115 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +struct ConnectionPoolOptions { + // Max number of connections, including both in-use and idle ones. + std::size_t size = 1; + + // Max time to wait for a connection. 0ms means client waits forever. + std::chrono::milliseconds wait_timeout{0}; + + // Max lifetime of a connection. 0ms means we never expire the connection. + std::chrono::milliseconds connection_lifetime{0}; +}; + +class ConnectionPool { +public: + ConnectionPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool(SimpleSentinel sentinel, + const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool() = default; + + ConnectionPool(ConnectionPool &&that); + ConnectionPool& operator=(ConnectionPool &&that); + + ConnectionPool(const ConnectionPool &) = delete; + ConnectionPool& operator=(const ConnectionPool &) = delete; + + ~ConnectionPool() = default; + + // Fetch a connection from pool. + Connection fetch(); + + ConnectionOptions connection_options(); + + void release(Connection connection); + + // Create a new connection. + Connection create(); + +private: + void _move(ConnectionPool &&that); + + // NOT thread-safe + Connection _create(); + + Connection _create(SimpleSentinel &sentinel, const ConnectionOptions &opts, bool locked); + + Connection _fetch(); + + void _wait_for_connection(std::unique_lock &lock); + + bool _need_reconnect(const Connection &connection, + const std::chrono::milliseconds &connection_lifetime) const; + + void _update_connection_opts(const std::string &host, int port) { + _opts.host = host; + _opts.port = port; + } + + bool _role_changed(const ConnectionOptions &opts) const { + return opts.port != _opts.port || opts.host != _opts.host; + } + + ConnectionOptions _opts; + + ConnectionPoolOptions _pool_opts; + + std::deque _pool; + + std::size_t _used_connections = 0; + + std::mutex _mutex; + + std::condition_variable _cv; + + SimpleSentinel _sentinel; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/errors.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/errors.h new file mode 100644 index 000000000..44d629e50 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/errors.h @@ -0,0 +1,159 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_ERRORS_H +#define SEWENEW_REDISPLUSPLUS_ERRORS_H + +#include +#include +#include + +namespace sw { + +namespace redis { + +enum ReplyErrorType { + ERR, + MOVED, + ASK +}; + +class Error : public std::exception { +public: + explicit Error(const std::string &msg) : _msg(msg) {} + + Error(const Error &) = default; + Error& operator=(const Error &) = default; + + Error(Error &&) = default; + Error& operator=(Error &&) = default; + + virtual ~Error() = default; + + virtual const char* what() const noexcept { + return _msg.data(); + } + +private: + std::string _msg; +}; + +class IoError : public Error { +public: + explicit IoError(const std::string &msg) : Error(msg) {} + + IoError(const IoError &) = default; + IoError& operator=(const IoError &) = default; + + IoError(IoError &&) = default; + IoError& operator=(IoError &&) = default; + + virtual ~IoError() = default; +}; + +class TimeoutError : public IoError { +public: + explicit TimeoutError(const std::string &msg) : IoError(msg) {} + + TimeoutError(const TimeoutError &) = default; + TimeoutError& operator=(const TimeoutError &) = default; + + TimeoutError(TimeoutError &&) = default; + TimeoutError& operator=(TimeoutError &&) = default; + + virtual ~TimeoutError() = default; +}; + +class ClosedError : public Error { +public: + explicit ClosedError(const std::string &msg) : Error(msg) {} + + ClosedError(const ClosedError &) = default; + ClosedError& operator=(const ClosedError &) = default; + + ClosedError(ClosedError &&) = default; + ClosedError& operator=(ClosedError &&) = default; + + virtual ~ClosedError() = default; +}; + +class ProtoError : public Error { +public: + explicit ProtoError(const std::string &msg) : Error(msg) {} + + ProtoError(const ProtoError &) = default; + ProtoError& operator=(const ProtoError &) = default; + + ProtoError(ProtoError &&) = default; + ProtoError& operator=(ProtoError &&) = default; + + virtual ~ProtoError() = default; +}; + +class OomError : public Error { +public: + explicit OomError(const std::string &msg) : Error(msg) {} + + OomError(const OomError &) = default; + OomError& operator=(const OomError &) = default; + + OomError(OomError &&) = default; + OomError& operator=(OomError &&) = default; + + virtual ~OomError() = default; +}; + +class ReplyError : public Error { +public: + explicit ReplyError(const std::string &msg) : Error(msg) {} + + ReplyError(const ReplyError &) = default; + ReplyError& operator=(const ReplyError &) = default; + + ReplyError(ReplyError &&) = default; + ReplyError& operator=(ReplyError &&) = default; + + virtual ~ReplyError() = default; +}; + +class WatchError : public Error { +public: + explicit WatchError() : Error("Watched key has been modified") {} + + WatchError(const WatchError &) = default; + WatchError& operator=(const WatchError &) = default; + + WatchError(WatchError &&) = default; + WatchError& operator=(WatchError &&) = default; + + virtual ~WatchError() = default; +}; + + +// MovedError and AskError are defined in shards.h +class MovedError; + +class AskError; + +void throw_error(redisContext &context, const std::string &err_info); + +void throw_error(const redisReply &reply); + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_ERRORS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/pipeline.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/pipeline.h new file mode 100644 index 000000000..52b01253f --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/pipeline.h @@ -0,0 +1,49 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_PIPELINE_H +#define SEWENEW_REDISPLUSPLUS_PIPELINE_H + +#include +#include +#include "connection.h" + +namespace sw { + +namespace redis { + +class PipelineImpl { +public: + template + void command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + } + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t /*cmd_num*/) { + // Reconnect to Redis to discard all commands. + connection.reconnect(); + } +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_PIPELINE_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.h new file mode 100644 index 000000000..71d975ee3 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.h @@ -0,0 +1,1844 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H + +#include +#include +#include +#include +#include "connection.h" +#include "utils.h" +#include "reply.h" +#include "command.h" +#include "redis.h" + +namespace sw { + +namespace redis { + +class QueuedReplies; + +// If any command throws, QueuedRedis resets the connection, and becomes invalid. +// In this case, the only thing we can do is to destory the QueuedRedis object. +template +class QueuedRedis { +public: + QueuedRedis(QueuedRedis &&) = default; + QueuedRedis& operator=(QueuedRedis &&) = default; + + // When it destructs, the underlying *Connection* will be closed, + // and any command that has NOT been executed will be ignored. + ~QueuedRedis() = default; + + Redis redis(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type; + + template + QueuedRedis& command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type; + + QueuedReplies exec(); + + void discard(); + + // CONNECTION commands. + + QueuedRedis& auth(const StringView &password) { + return command(cmd::auth, password); + } + + QueuedRedis& echo(const StringView &msg) { + return command(cmd::echo, msg); + } + + QueuedRedis& ping() { + return command(cmd::ping); + } + + QueuedRedis& ping(const StringView &msg) { + return command(cmd::ping, msg); + } + + // We DO NOT support the QUIT command. See *Redis::quit* doc for details. + // + // QueuedRedis& quit(); + + QueuedRedis& select(long long idx) { + return command(cmd::select, idx); + } + + QueuedRedis& swapdb(long long idx1, long long idx2) { + return command(cmd::swapdb, idx1, idx2); + } + + // SERVER commands. + + QueuedRedis& bgrewriteaof() { + return command(cmd::bgrewriteaof); + } + + QueuedRedis& bgsave() { + return command(cmd::bgsave); + } + + QueuedRedis& dbsize() { + return command(cmd::dbsize); + } + + QueuedRedis& flushall(bool async = false) { + return command(cmd::flushall, async); + } + + QueuedRedis& flushdb(bool async = false) { + return command(cmd::flushdb, async); + } + + QueuedRedis& info() { + return command(cmd::info); + } + + QueuedRedis& info(const StringView §ion) { + return command(cmd::info, section); + } + + QueuedRedis& lastsave() { + return command(cmd::lastsave); + } + + QueuedRedis& save() { + return command(cmd::save); + } + + // KEY commands. + + QueuedRedis& del(const StringView &key) { + return command(cmd::del, key); + } + + template + QueuedRedis& del(Input first, Input last) { + return command(cmd::del_range, first, last); + } + + template + QueuedRedis& del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + QueuedRedis& dump(const StringView &key) { + return command(cmd::dump, key); + } + + QueuedRedis& exists(const StringView &key) { + return command(cmd::exists, key); + } + + template + QueuedRedis& exists(Input first, Input last) { + return command(cmd::exists_range, first, last); + } + + template + QueuedRedis& exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + QueuedRedis& expire(const StringView &key, long long timeout) { + return command(cmd::expire, key, timeout); + } + + QueuedRedis& expire(const StringView &key, + const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); + } + + QueuedRedis& expireat(const StringView &key, long long timestamp) { + return command(cmd::expireat, key, timestamp); + } + + QueuedRedis& expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& keys(const StringView &pattern) { + return command(cmd::keys, pattern); + } + + QueuedRedis& move(const StringView &key, long long db) { + return command(cmd::move, key, db); + } + + QueuedRedis& persist(const StringView &key) { + return command(cmd::persist, key); + } + + QueuedRedis& pexpire(const StringView &key, long long timeout) { + return command(cmd::pexpire, key, timeout); + } + + QueuedRedis& pexpire(const StringView &key, + const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); + } + + QueuedRedis& pexpireat(const StringView &key, long long timestamp) { + return command(cmd::pexpireat, key, timestamp); + } + + QueuedRedis& pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& pttl(const StringView &key) { + return command(cmd::pttl, key); + } + + QueuedRedis& randomkey() { + return command(cmd::randomkey); + } + + QueuedRedis& rename(const StringView &key, const StringView &newkey) { + return command(cmd::rename, key, newkey); + } + + QueuedRedis& renamenx(const StringView &key, const StringView &newkey) { + return command(cmd::renamenx, key, newkey); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false) { + return command(cmd::restore, key, val, ttl, replace); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false) { + return restore(key, val, ttl.count(), replace); + } + + // TODO: sort + + QueuedRedis& scan(long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::scan, cursor, pattern, count); + } + + QueuedRedis& scan(long long cursor) { + return scan(cursor, "*", 10); + } + + QueuedRedis& scan(long long cursor, + const StringView &pattern) { + return scan(cursor, pattern, 10); + } + + QueuedRedis& scan(long long cursor, + long long count) { + return scan(cursor, "*", count); + } + + QueuedRedis& touch(const StringView &key) { + return command(cmd::touch, key); + } + + template + QueuedRedis& touch(Input first, Input last) { + return command(cmd::touch_range, first, last); + } + + template + QueuedRedis& touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + QueuedRedis& ttl(const StringView &key) { + return command(cmd::ttl, key); + } + + QueuedRedis& type(const StringView &key) { + return command(cmd::type, key); + } + + QueuedRedis& unlink(const StringView &key) { + return command(cmd::unlink, key); + } + + template + QueuedRedis& unlink(Input first, Input last) { + return command(cmd::unlink_range, first, last); + } + + template + QueuedRedis& unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + QueuedRedis& wait(long long numslaves, long long timeout) { + return command(cmd::wait, numslaves, timeout); + } + + QueuedRedis& wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); + } + + // STRING commands. + + QueuedRedis& append(const StringView &key, const StringView &str) { + return command(cmd::append, key, str); + } + + QueuedRedis& bitcount(const StringView &key, + long long start = 0, + long long end = -1) { + return command(cmd::bitcount, key, start, end); + } + + QueuedRedis& bitop(BitOp op, + const StringView &destination, + const StringView &key) { + return command(cmd::bitop, op, destination, key); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + Input first, + Input last) { + return command(cmd::bitop_range, op, destination, first, last); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + QueuedRedis& bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1) { + return command(cmd::bitpos, key, bit, start, end); + } + + QueuedRedis& decr(const StringView &key) { + return command(cmd::decr, key); + } + + QueuedRedis& decrby(const StringView &key, long long decrement) { + return command(cmd::decrby, key, decrement); + } + + QueuedRedis& get(const StringView &key) { + return command(cmd::get, key); + } + + QueuedRedis& getbit(const StringView &key, long long offset) { + return command(cmd::getbit, key, offset); + } + + QueuedRedis& getrange(const StringView &key, long long start, long long end) { + return command(cmd::getrange, key, start, end); + } + + QueuedRedis& getset(const StringView &key, const StringView &val) { + return command(cmd::getset, key, val); + } + + QueuedRedis& incr(const StringView &key) { + return command(cmd::incr, key); + } + + QueuedRedis& incrby(const StringView &key, long long increment) { + return command(cmd::incrby, key, increment); + } + + QueuedRedis& incrbyfloat(const StringView &key, double increment) { + return command(cmd::incrbyfloat, key, increment); + } + + template + QueuedRedis& mget(Input first, Input last) { + return command(cmd::mget, first, last); + } + + template + QueuedRedis& mget(std::initializer_list il) { + return mget(il.begin(), il.end()); + } + + template + QueuedRedis& mset(Input first, Input last) { + return command(cmd::mset, first, last); + } + + template + QueuedRedis& mset(std::initializer_list il) { + return mset(il.begin(), il.end()); + } + + template + QueuedRedis& msetnx(Input first, Input last) { + return command(cmd::msetnx, first, last); + } + + template + QueuedRedis& msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + QueuedRedis& psetex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::psetex, key, ttl, val); + } + + QueuedRedis& psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); + } + + QueuedRedis& set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS) { + _set_cmd_indexes.push_back(_cmd_num); + + return command(cmd::set, key, val, ttl.count(), type); + } + + QueuedRedis& setex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::setex, key, ttl, val); + } + + QueuedRedis& setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + return setex(key, ttl.count(), val); + } + + QueuedRedis& setnx(const StringView &key, const StringView &val) { + return command(cmd::setnx, key, val); + } + + QueuedRedis& setrange(const StringView &key, + long long offset, + const StringView &val) { + return command(cmd::setrange, key, offset, val); + } + + QueuedRedis& strlen(const StringView &key) { + return command(cmd::strlen, key); + } + + // LIST commands. + + QueuedRedis& blpop(const StringView &key, long long timeout) { + return command(cmd::blpop, key, timeout); + } + + QueuedRedis& blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(key, timeout.count()); + } + + template + QueuedRedis& blpop(Input first, Input last, long long timeout) { + return command(cmd::blpop_range, first, last, timeout); + } + + template + QueuedRedis& blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(first, last, timeout.count()); + } + + template + QueuedRedis& blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpop(const StringView &key, long long timeout) { + return command(cmd::brpop, key, timeout); + } + + QueuedRedis& brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(key, timeout.count()); + } + + template + QueuedRedis& brpop(Input first, Input last, long long timeout) { + return command(cmd::brpop_range, first, last, timeout); + } + + template + QueuedRedis& brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(first, last, timeout.count()); + } + + template + QueuedRedis& brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + long long timeout) { + return command(cmd::brpoplpush, source, destination, timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpoplpush(source, destination, timeout.count()); + } + + QueuedRedis& lindex(const StringView &key, long long index) { + return command(cmd::lindex, key, index); + } + + QueuedRedis& linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val) { + return command(cmd::linsert, key, position, pivot, val); + } + + QueuedRedis& llen(const StringView &key) { + return command(cmd::llen, key); + } + + QueuedRedis& lpop(const StringView &key) { + return command(cmd::lpop, key); + } + + QueuedRedis& lpush(const StringView &key, const StringView &val) { + return command(cmd::lpush, key, val); + } + + template + QueuedRedis& lpush(const StringView &key, Input first, Input last) { + return command(cmd::lpush_range, key, first, last); + } + + template + QueuedRedis& lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + QueuedRedis& lpushx(const StringView &key, const StringView &val) { + return command(cmd::lpushx, key, val); + } + + QueuedRedis& lrange(const StringView &key, + long long start, + long long stop) { + return command(cmd::lrange, key, start, stop); + } + + QueuedRedis& lrem(const StringView &key, long long count, const StringView &val) { + return command(cmd::lrem, key, count, val); + } + + QueuedRedis& lset(const StringView &key, long long index, const StringView &val) { + return command(cmd::lset, key, index, val); + } + + QueuedRedis& ltrim(const StringView &key, long long start, long long stop) { + return command(cmd::ltrim, key, start, stop); + } + + QueuedRedis& rpop(const StringView &key) { + return command(cmd::rpop, key); + } + + QueuedRedis& rpoplpush(const StringView &source, const StringView &destination) { + return command(cmd::rpoplpush, source, destination); + } + + QueuedRedis& rpush(const StringView &key, const StringView &val) { + return command(cmd::rpush, key, val); + } + + template + QueuedRedis& rpush(const StringView &key, Input first, Input last) { + return command(cmd::rpush_range, key, first, last); + } + + template + QueuedRedis& rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + QueuedRedis& rpushx(const StringView &key, const StringView &val) { + return command(cmd::rpushx, key, val); + } + + // HASH commands. + + QueuedRedis& hdel(const StringView &key, const StringView &field) { + return command(cmd::hdel, key, field); + } + + template + QueuedRedis& hdel(const StringView &key, Input first, Input last) { + return command(cmd::hdel_range, key, first, last); + } + + template + QueuedRedis& hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + QueuedRedis& hexists(const StringView &key, const StringView &field) { + return command(cmd::hexists, key, field); + } + + QueuedRedis& hget(const StringView &key, const StringView &field) { + return command(cmd::hget, key, field); + } + + QueuedRedis& hgetall(const StringView &key) { + return command(cmd::hgetall, key); + } + + QueuedRedis& hincrby(const StringView &key, + const StringView &field, + long long increment) { + return command(cmd::hincrby, key, field, increment); + } + + QueuedRedis& hincrbyfloat(const StringView &key, + const StringView &field, + double increment) { + return command(cmd::hincrbyfloat, key, field, increment); + } + + QueuedRedis& hkeys(const StringView &key) { + return command(cmd::hkeys, key); + } + + QueuedRedis& hlen(const StringView &key) { + return command(cmd::hlen, key); + } + + template + QueuedRedis& hmget(const StringView &key, Input first, Input last) { + return command(cmd::hmget, key, first, last); + } + + template + QueuedRedis& hmget(const StringView &key, std::initializer_list il) { + return hmget(key, il.begin(), il.end()); + } + + template + QueuedRedis& hmset(const StringView &key, Input first, Input last) { + return command(cmd::hmset, key, first, last); + } + + template + QueuedRedis& hmset(const StringView &key, std::initializer_list il) { + return hmset(key, il.begin(), il.end()); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::hscan, key, cursor, pattern, count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return hscan(key, cursor, pattern, 10); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + long long count) { + return hscan(key, cursor, "*", count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor) { + return hscan(key, cursor, "*", 10); + } + + QueuedRedis& hset(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hset, key, field, val); + } + + QueuedRedis& hset(const StringView &key, const std::pair &item) { + return hset(key, item.first, item.second); + } + + QueuedRedis& hsetnx(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hsetnx, key, field, val); + } + + QueuedRedis& hsetnx(const StringView &key, const std::pair &item) { + return hsetnx(key, item.first, item.second); + } + + QueuedRedis& hstrlen(const StringView &key, const StringView &field) { + return command(cmd::hstrlen, key, field); + } + + QueuedRedis& hvals(const StringView &key) { + return command(cmd::hvals, key); + } + + // SET commands. + + QueuedRedis& sadd(const StringView &key, const StringView &member) { + return command(cmd::sadd, key, member); + } + + template + QueuedRedis& sadd(const StringView &key, Input first, Input last) { + return command(cmd::sadd_range, key, first, last); + } + + template + QueuedRedis& sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + QueuedRedis& scard(const StringView &key) { + return command(cmd::scard, key); + } + + template + QueuedRedis& sdiff(Input first, Input last) { + return command(cmd::sdiff, first, last); + } + + template + QueuedRedis& sdiff(std::initializer_list il) { + return sdiff(il.begin(), il.end()); + } + + QueuedRedis& sdiffstore(const StringView &destination, const StringView &key) { + return command(cmd::sdiffstore, destination, key); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, + Input first, + Input last) { + return command(cmd::sdiffstore_range, destination, first, last); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + QueuedRedis& sinter(Input first, Input last) { + return command(cmd::sinter, first, last); + } + + template + QueuedRedis& sinter(std::initializer_list il) { + return sinter(il.begin(), il.end()); + } + + QueuedRedis& sinterstore(const StringView &destination, const StringView &key) { + return command(cmd::sinterstore, destination, key); + } + + template + QueuedRedis& sinterstore(const StringView &destination, + Input first, + Input last) { + return command(cmd::sinterstore_range, destination, first, last); + } + + template + QueuedRedis& sinterstore(const StringView &destination, std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + QueuedRedis& sismember(const StringView &key, const StringView &member) { + return command(cmd::sismember, key, member); + } + + QueuedRedis& smembers(const StringView &key) { + return command(cmd::smembers, key); + } + + QueuedRedis& smove(const StringView &source, + const StringView &destination, + const StringView &member) { + return command(cmd::smove, source, destination, member); + } + + QueuedRedis& spop(const StringView &key) { + return command(cmd::spop, key); + } + + QueuedRedis& spop(const StringView &key, long long count) { + return command(cmd::spop_range, key, count); + } + + QueuedRedis& srandmember(const StringView &key) { + return command(cmd::srandmember, key); + } + + QueuedRedis& srandmember(const StringView &key, long long count) { + return command(cmd::srandmember_range, key, count); + } + + QueuedRedis& srem(const StringView &key, const StringView &member) { + return command(cmd::srem, key, member); + } + + template + QueuedRedis& srem(const StringView &key, Input first, Input last) { + return command(cmd::srem_range, key, first, last); + } + + template + QueuedRedis& srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::sscan, key, cursor, pattern, count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return sscan(key, cursor, pattern, 10); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + long long count) { + return sscan(key, cursor, "*", count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor) { + return sscan(key, cursor, "*", 10); + } + + template + QueuedRedis& sunion(Input first, Input last) { + return command(cmd::sunion, first, last); + } + + template + QueuedRedis& sunion(std::initializer_list il) { + return sunion(il.begin(), il.end()); + } + + QueuedRedis& sunionstore(const StringView &destination, const StringView &key) { + return command(cmd::sunionstore, destination, key); + } + + template + QueuedRedis& sunionstore(const StringView &destination, Input first, Input last) { + return command(cmd::sunionstore_range, destination, first, last); + } + + template + QueuedRedis& sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + QueuedRedis& bzpopmax(const StringView &key, long long timeout) { + return command(cmd::bzpopmax, key, timeout); + } + + QueuedRedis& bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(key, timeout.count()); + } + + template + QueuedRedis& bzpopmax(Input first, Input last, long long timeout) { + return command(cmd::bzpopmax_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, long long timeout) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, long long timeout) { + return command(cmd::bzpopmin, key, timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(key, timeout.count()); + } + + template + QueuedRedis& bzpopmin(Input first, Input last, long long timeout) { + return command(cmd::bzpopmin_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, long long timeout) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + QueuedRedis& zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return command(cmd::zadd, key, member, score, type, changed); + } + + template + QueuedRedis& zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return command(cmd::zadd_range, key, first, last, type, changed); + } + + QueuedRedis& zcard(const StringView &key) { + return command(cmd::zcard, key); + } + + template + QueuedRedis& zcount(const StringView &key, const Interval &interval) { + return command(cmd::zcount, key, interval); + } + + QueuedRedis& zincrby(const StringView &key, double increment, const StringView &member) { + return command(cmd::zincrby, key, increment, member); + } + + QueuedRedis& zinterstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zinterstore, destination, key, weight); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + return command(cmd::zinterstore_range, destination, first, last, type); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + QueuedRedis& zlexcount(const StringView &key, const Interval &interval) { + return command(cmd::zlexcount, key, interval); + } + + QueuedRedis& zpopmax(const StringView &key) { + return command(cmd::zpopmax, key, 1); + } + + QueuedRedis& zpopmax(const StringView &key, long long count) { + return command(cmd::zpopmax, key, count); + } + + QueuedRedis& zpopmin(const StringView &key) { + return command(cmd::zpopmin, key, 1); + } + + QueuedRedis& zpopmin(const StringView &key, long long count) { + return command(cmd::zpopmin, key, count); + } + + // NOTE: *QueuedRedis::zrange*'s parameters are different from *Redis::zrange*. + // *Redis::zrange* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::zrange*. So we have to use an extra parameter: *with_scores*, + // to decide whether we should send *WITHSCORES* option to Redis. This also applies to + // other commands with the *WITHSCORES* option, e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, + // *ZREVRANGEBYSCORE*. + QueuedRedis& zrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrangebylex(const StringView &key, const Interval &interval) { + return zrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrank(const StringView &key, const StringView &member) { + return command(cmd::zrank, key, member); + } + + QueuedRedis& zrem(const StringView &key, const StringView &member) { + return command(cmd::zrem, key, member); + } + + template + QueuedRedis& zrem(const StringView &key, Input first, Input last) { + return command(cmd::zrem_range, key, first, last); + } + + template + QueuedRedis& zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + QueuedRedis& zremrangebylex(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebylex, key, interval); + } + + QueuedRedis& zremrangebyrank(const StringView &key, long long start, long long stop) { + return command(cmd::zremrangebyrank, key, start, stop); + } + + template + QueuedRedis& zremrangebyscore(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebyscore, key, interval); + } + + // See comments on *ZRANGE*. + QueuedRedis& zrevrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrevrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrevrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, const Interval &interval) { + return zrevrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrevrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrevrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrevrank(const StringView &key, const StringView &member) { + return command(cmd::zrevrank, key, member); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::zscan, key, cursor, pattern, count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return zscan(key, cursor, pattern, 10); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + long long count) { + return zscan(key, cursor, "*", count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor) { + return zscan(key, cursor, "*", 10); + } + + QueuedRedis& zscore(const StringView &key, const StringView &member) { + return command(cmd::zscore, key, member); + } + + QueuedRedis& zunionstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zunionstore, destination, key, weight); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + return command(cmd::zunionstore_range, destination, first, last, type); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + QueuedRedis& pfadd(const StringView &key, const StringView &element) { + return command(cmd::pfadd, key, element); + } + + template + QueuedRedis& pfadd(const StringView &key, Input first, Input last) { + return command(cmd::pfadd_range, key, first, last); + } + + template + QueuedRedis& pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + QueuedRedis& pfcount(const StringView &key) { + return command(cmd::pfcount, key); + } + + template + QueuedRedis& pfcount(Input first, Input last) { + return command(cmd::pfcount_range, first, last); + } + + template + QueuedRedis& pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + QueuedRedis& pfmerge(const StringView &destination, const StringView &key) { + return command(cmd::pfmerge, destination, key); + } + + template + QueuedRedis& pfmerge(const StringView &destination, Input first, Input last) { + return command(cmd::pfmerge_range, destination, first, last); + } + + template + QueuedRedis& pfmerge(const StringView &destination, std::initializer_list il) { + return pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + QueuedRedis& geoadd(const StringView &key, + const std::tuple &member) { + return command(cmd::geoadd, key, member); + } + + template + QueuedRedis& geoadd(const StringView &key, + Input first, + Input last) { + return command(cmd::geoadd_range, key, first, last); + } + + template + QueuedRedis& geoadd(const StringView &key, std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + QueuedRedis& geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M) { + return command(cmd::geodist, key, member1, member2, unit); + } + + template + QueuedRedis& geohash(const StringView &key, Input first, Input last) { + return command(cmd::geohash_range, key, first, last); + } + + template + QueuedRedis& geohash(const StringView &key, std::initializer_list il) { + return geohash(key, il.begin(), il.end()); + } + + template + QueuedRedis& geopos(const StringView &key, Input first, Input last) { + return command(cmd::geopos_range, key, first, last); + } + + template + QueuedRedis& geopos(const StringView &key, std::initializer_list il) { + return geopos(key, il.begin(), il.end()); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _georadius_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadius_store, + key, + loc, + radius, + unit, + destination, + store_dist, + count); + } + + // NOTE: *QueuedRedis::georadius*'s parameters are different from *Redis::georadius*. + // *Redis::georadius* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::georadius*. So we have to use extra parameters to decide + // whether we should send options to Redis. This also applies to *GEORADIUSBYMEMBER*. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _georadius_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + destination, + store_dist, + count); + } + + // See the comments on *GEORADIUS*. + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + // SCRIPTING commands. + + QueuedRedis& eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return command(cmd::eval, script, keys, args); + } + + QueuedRedis& evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return command(cmd::evalsha, script, keys, args); + } + + template + QueuedRedis& script_exists(Input first, Input last) { + return command(cmd::script_exists_range, first, last); + } + + template + QueuedRedis& script_exists(std::initializer_list il) { + return script_exists(il.begin(), il.end()); + } + + QueuedRedis& script_flush() { + return command(cmd::script_flush); + } + + QueuedRedis& script_kill() { + return command(cmd::script_kill); + } + + QueuedRedis& script_load(const StringView &script) { + return command(cmd::script_load, script); + } + + // PUBSUB commands. + + QueuedRedis& publish(const StringView &channel, const StringView &message) { + return command(cmd::publish, channel, message); + } + + // Stream commands. + + QueuedRedis& xack(const StringView &key, const StringView &group, const StringView &id) { + return command(cmd::xack, key, group, id); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, Input first, Input last) { + return command(cmd::xack_range, key, group, first, last); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, Input first, Input last) { + return command(cmd::xadd_range, key, id, first, last); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true) { + return command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id) { + return command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last) { + return command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il) { + return xclaim(key, group, consumer, min_idle_time, il.begin(), il.end()); + } + + QueuedRedis& xdel(const StringView &key, const StringView &id) { + return command(cmd::xdel, key, id); + } + + template + QueuedRedis& xdel(const StringView &key, Input first, Input last) { + return command(cmd::xdel_range, key, first, last); + } + + template + QueuedRedis& xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + QueuedRedis& xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false) { + return command(cmd::xgroup_create, key, group, id, mkstream); + } + + QueuedRedis& xgroup_setid(const StringView &key, + const StringView &group, + const StringView &id) { + return command(cmd::xgroup_setid, key, group, id); + } + + QueuedRedis& xgroup_destroy(const StringView &key, const StringView &group) { + return command(cmd::xgroup_destroy, key, group); + } + + QueuedRedis& xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer) { + return command(cmd::xgroup_delconsumer, key, group, consumer); + } + + QueuedRedis& xlen(const StringView &key) { + return command(cmd::xlen, key); + } + + QueuedRedis& xpending(const StringView &key, const StringView &group) { + return command(cmd::xpending, key, group); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xpending_detail, key, group, start, end, count); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + return command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end) { + return command(cmd::xrange, key, start, end); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xrange, key, start, end, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id, long long count) { + return command(cmd::xread, key, id, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id) { + return xread(key, id, 0); + } + + template + auto xread(Input first, Input last, long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xread_range, first, last, count); + } + + template + auto xread(Input first, Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, 0); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return command(cmd::xread_block, key, id, timeout.count(), count); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xread(key, id, timeout, 0); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xread_block_range, first, last, timeout.count(), count); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, timeout, 0); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + return command(cmd::xreadgroup, group, consumer, key, id, count, noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count) { + return xreadgroup(group, consumer, key, id, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, 0, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) { + return command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return xreadgroup(group, consumer, key, id, timeout, count, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xreadgroup(group, consumer, key, id, timeout, 0, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, 0, false); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start) { + return command(cmd::xrevrange, key, end, start); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + return command(cmd::xrevrange, key, end, start, count); + } + + QueuedRedis& xtrim(const StringView &key, long long count, bool approx = true) { + return command(cmd::xtrim, key, count, approx); + } + +private: + friend class Redis; + + friend class RedisCluster; + + template + QueuedRedis(const ConnectionSPtr &connection, Args &&...args); + + void _sanity_check() const; + + void _reset(); + + void _invalidate(); + + void _rewrite_replies(std::vector &replies) const; + + template + void _rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const; + + ConnectionSPtr _connection; + + Impl _impl; + + std::size_t _cmd_num = 0; + + std::vector _set_cmd_indexes; + + std::vector _georadius_cmd_indexes; + + bool _valid = true; +}; + +class QueuedReplies { +public: + std::size_t size() const; + + redisReply& get(std::size_t idx); + + template + Result get(std::size_t idx); + + template + void get(std::size_t idx, Output output); + +private: + template + friend class QueuedRedis; + + explicit QueuedReplies(std::vector replies) : _replies(std::move(replies)) {} + + void _index_check(std::size_t idx) const; + + std::vector _replies; +}; + +} + +} + +#include "queued_redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.hpp b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.hpp new file mode 100644 index 000000000..409f48aca --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.hpp @@ -0,0 +1,208 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP + +namespace sw { + +namespace redis { + +template +template +QueuedRedis::QueuedRedis(const ConnectionSPtr &connection, Args &&...args) : + _connection(connection), + _impl(std::forward(args)...) { + assert(_connection); +} + +template +Redis QueuedRedis::redis() { + return Redis(_connection); +} + +template +template +auto QueuedRedis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type { + try { + _sanity_check(); + + _impl.command(*_connection, cmd, std::forward(args)...); + + ++_cmd_num; + } catch (const Error &e) { + _invalidate(); + throw; + } + + return *this; +} + +template +template +QueuedRedis& QueuedRedis::command(const StringView &cmd_name, Args &&...args) { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +template +auto QueuedRedis::command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type { + if (first == last) { + throw Error("command: empty range"); + } + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +QueuedReplies QueuedRedis::exec() { + try { + _sanity_check(); + + auto replies = _impl.exec(*_connection, _cmd_num); + + _rewrite_replies(replies); + + _reset(); + + return QueuedReplies(std::move(replies)); + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +void QueuedRedis::discard() { + try { + _sanity_check(); + + _impl.discard(*_connection, _cmd_num); + + _reset(); + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +void QueuedRedis::_sanity_check() const { + if (!_valid) { + throw Error("Not in valid state"); + } + + if (_connection->broken()) { + throw Error("Connection is broken"); + } +} + +template +inline void QueuedRedis::_reset() { + _cmd_num = 0; + + _set_cmd_indexes.clear(); + + _georadius_cmd_indexes.clear(); +} + +template +void QueuedRedis::_invalidate() { + _valid = false; + + _reset(); +} + +template +void QueuedRedis::_rewrite_replies(std::vector &replies) const { + _rewrite_replies(_set_cmd_indexes, reply::rewrite_set_reply, replies); + + _rewrite_replies(_georadius_cmd_indexes, reply::rewrite_georadius_reply, replies); +} + +template +template +void QueuedRedis::_rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const { + for (auto idx : indexes) { + assert(idx < replies.size()); + + auto &reply = replies[idx]; + + assert(reply); + + rewriter(*reply); + } +} + +inline std::size_t QueuedReplies::size() const { + return _replies.size(); +} + +inline redisReply& QueuedReplies::get(std::size_t idx) { + _index_check(idx); + + auto &reply = _replies[idx]; + + assert(reply); + + return *reply; +} + +template +inline Result QueuedReplies::get(std::size_t idx) { + auto &reply = get(idx); + + return reply::parse(reply); +} + +template +inline void QueuedReplies::get(std::size_t idx, Output output) { + auto &reply = get(idx); + + reply::to_array(reply, output); +} + +inline void QueuedReplies::_index_check(std::size_t idx) const { + if (idx >= size()) { + throw Error("Out of range"); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis++.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis++.h new file mode 100644 index 000000000..0da0ebb16 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis++.h @@ -0,0 +1,25 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H +#define SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H + +#include "redis.h" +#include "redis_cluster.h" +#include "queued_redis.h" +#include "sentinel.h" + +#endif // end SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.h new file mode 100644 index 000000000..b54afb96b --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.h @@ -0,0 +1,1523 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_H +#define SEWENEW_REDISPLUSPLUS_REDIS_H + +#include +#include +#include +#include +#include +#include "connection_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class Redis { +public: + Redis(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : _pool(pool_opts, connection_opts) {} + + // Construct Redis instance with URI: + // "tcp://127.0.0.1", "tcp://127.0.0.1:6379", or "unix://path/to/socket" + explicit Redis(const std::string &uri); + + Redis(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role, + const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(SimpleSentinel(sentinel, master_name, role), pool_opts, connection_opts) {} + + Redis(const Redis &) = delete; + Redis& operator=(const Redis &) = delete; + + Redis(Redis &&) = default; + Redis& operator=(Redis &&) = default; + + Pipeline pipeline(); + + Transaction transaction(bool piped = false); + + Subscriber subscriber(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type; + + template + Result command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // CONNECTION commands. + + void auth(const StringView &password); + + std::string echo(const StringView &msg); + + std::string ping(); + + std::string ping(const StringView &msg); + + // After sending QUIT, only the current connection will be close, while + // other connections in the pool is still open. This is a strange behavior. + // So we DO NOT support the QUIT command. If you want to quit connection to + // server, just destroy the Redis object. + // + // void quit(); + + // We get a connection from the pool, and send the SELECT command to switch + // to a specified DB. However, when we try to send other commands to the + // given DB, we might get a different connection from the pool, and these + // commands, in fact, work on other DB. e.g. + // + // redis.select(1); // get a connection from the pool and switch to the 1th DB + // redis.get("key"); // might get another connection from the pool, + // // and try to get 'key' on the default DB + // + // Obviously, this is NOT what we expect. So we DO NOT support SELECT command. + // In order to select a DB, we can specify the DB index with the ConnectionOptions. + // + // However, since Pipeline and Transaction always send multiple commands on a + // single connection, these two classes have a *select* method. + // + // void select(long long idx); + + void swapdb(long long idx1, long long idx2); + + // SERVER commands. + + void bgrewriteaof(); + + void bgsave(); + + long long dbsize(); + + void flushall(bool async = false); + + void flushdb(bool async = false); + + std::string info(); + + std::string info(const StringView §ion); + + long long lastsave(); + + void save(); + + // KEY commands. + + long long del(const StringView &key); + + template + long long del(Input first, Input last); + + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + OptionalString dump(const StringView &key); + + long long exists(const StringView &key); + + template + long long exists(Input first, Input last); + + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + bool expire(const StringView &key, long long timeout); + + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + bool expireat(const StringView &key, long long timestamp); + + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + template + void keys(const StringView &pattern, Output output); + + bool move(const StringView &key, long long db); + + bool persist(const StringView &key); + + bool pexpire(const StringView &key, long long timeout); + + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + bool pexpireat(const StringView &key, long long timestamp); + + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + long long pttl(const StringView &key); + + OptionalString randomkey(); + + void rename(const StringView &key, const StringView &newkey); + + bool renamenx(const StringView &key, const StringView &newkey); + + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + template + long long scan(long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long scan(long long cursor, + Output output); + + template + long long scan(long long cursor, + const StringView &pattern, + Output output); + + template + long long scan(long long cursor, + long long count, + Output output); + + long long touch(const StringView &key); + + template + long long touch(Input first, Input last); + + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + long long ttl(const StringView &key); + + std::string type(const StringView &key); + + long long unlink(const StringView &key); + + template + long long unlink(Input first, Input last); + + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + long long wait(long long numslaves, long long timeout); + + long long wait(long long numslaves, const std::chrono::milliseconds &timeout); + + // STRING commands. + + long long append(const StringView &key, const StringView &str); + + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + long long decr(const StringView &key); + + long long decrby(const StringView &key, long long decrement); + + OptionalString get(const StringView &key); + + long long getbit(const StringView &key, long long offset); + + std::string getrange(const StringView &key, long long start, long long end); + + OptionalString getset(const StringView &key, const StringView &val); + + long long incr(const StringView &key); + + long long incrby(const StringView &key, long long increment); + + double incrbyfloat(const StringView &key, double increment); + + template + void mget(Input first, Input last, Output output); + + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + template + void mset(Input first, Input last); + + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + template + bool msetnx(Input first, Input last); + + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + void setex(const StringView &key, + long long ttl, + const StringView &val); + + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + bool setnx(const StringView &key, const StringView &val); + + long long setrange(const StringView &key, long long offset, const StringView &val); + + long long strlen(const StringView &key); + + // LIST commands. + + OptionalStringPair blpop(const StringView &key, long long timeout); + + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + OptionalStringPair brpop(const StringView &key, long long timeout); + + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + OptionalString lindex(const StringView &key, long long index); + + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + long long llen(const StringView &key); + + OptionalString lpop(const StringView &key); + + long long lpush(const StringView &key, const StringView &val); + + template + long long lpush(const StringView &key, Input first, Input last); + + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + long long lpushx(const StringView &key, const StringView &val); + + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + long long lrem(const StringView &key, long long count, const StringView &val); + + void lset(const StringView &key, long long index, const StringView &val); + + void ltrim(const StringView &key, long long start, long long stop); + + OptionalString rpop(const StringView &key); + + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + long long rpush(const StringView &key, const StringView &val); + + template + long long rpush(const StringView &key, Input first, Input last); + + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + long long hdel(const StringView &key, const StringView &field); + + template + long long hdel(const StringView &key, Input first, Input last); + + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + bool hexists(const StringView &key, const StringView &field); + + OptionalString hget(const StringView &key, const StringView &field); + + template + void hgetall(const StringView &key, Output output); + + long long hincrby(const StringView &key, const StringView &field, long long increment); + + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + template + void hkeys(const StringView &key, Output output); + + long long hlen(const StringView &key); + + template + void hmget(const StringView &key, Input first, Input last, Output output); + + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + template + void hmset(const StringView &key, Input first, Input last); + + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + bool hset(const StringView &key, const StringView &field, const StringView &val); + + bool hset(const StringView &key, const std::pair &item); + + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + bool hsetnx(const StringView &key, const std::pair &item); + + long long hstrlen(const StringView &key, const StringView &field); + + template + void hvals(const StringView &key, Output output); + + // SET commands. + + long long sadd(const StringView &key, const StringView &member); + + template + long long sadd(const StringView &key, Input first, Input last); + + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + long long scard(const StringView &key); + + template + void sdiff(Input first, Input last, Output output); + + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + long long sdiffstore(const StringView &destination, const StringView &key); + + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + void sinter(Input first, Input last, Output output); + + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + long long sinterstore(const StringView &destination, const StringView &key); + + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + bool sismember(const StringView &key, const StringView &member); + + template + void smembers(const StringView &key, Output output); + + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + OptionalString spop(const StringView &key); + + template + void spop(const StringView &key, long long count, Output output); + + OptionalString srandmember(const StringView &key); + + template + void srandmember(const StringView &key, long long count, Output output); + + long long srem(const StringView &key, const StringView &member); + + template + long long srem(const StringView &key, Input first, Input last); + + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + template + void sunion(Input first, Input last, Output output); + + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + long long sunionstore(const StringView &destination, const StringView &key); + + template + long long sunionstore(const StringView &destination, Input first, Input last); + + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + long long zcard(const StringView &key); + + template + long long zcount(const StringView &key, const Interval &interval); + + double zincrby(const StringView &key, double increment, const StringView &member); + + // There's no aggregation type parameter for single key overload, since these 3 types + // have the same effect. + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + // If *Input* is an iterator of a container of string, + // we use the default weight, i.e. 1, and send + // *ZINTERSTORE destination numkeys key [key ...] [AGGREGATE SUM|MIN|MAX]* command. + // If *Input* is an iterator of a container of pair, i.e. key-weight pair, + // we send the command with the given weights: + // *ZINTERSTORE destination numkeys key [key ...] + // [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]* + // + // The following code use the default weight: + // + // vector keys = {"k1", "k2", "k3"}; + // redis.zinterstore(destination, keys.begin(), keys.end()); + // + // On the other hand, the following code use the given weights: + // + // vector> keys_with_weights = {{"k1", 1}, {"k2", 2}, {"k3", 3}}; + // redis.zinterstore(destination, keys_with_weights.begin(), keys_with_weights.end()); + // + // NOTE: `keys_with_weights` can also be of type `unordered_map`. + // However, it will be slower than vector>, since we use + // `distance(first, last)` to calculate the *numkeys* parameter. + // + // This also applies to *ZUNIONSTORE* command. + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + long long zlexcount(const StringView &key, const Interval &interval); + + Optional> zpopmax(const StringView &key); + + template + void zpopmax(const StringView &key, long long count, Output output); + + Optional> zpopmin(const StringView &key); + + template + void zpopmin(const StringView &key, long long count, Output output); + + // If *output* is an iterator of a container of string, + // we send *ZRANGE key start stop* command. + // If it's an iterator of a container of pair, + // we send *ZRANGE key start stop WITHSCORES* command. + // + // The following code sends *ZRANGE* without the *WITHSCORES* option: + // + // vector result; + // redis.zrange("key", 0, -1, back_inserter(result)); + // + // On the other hand, the following code sends command with *WITHSCORES* option: + // + // unordered_map with_score; + // redis.zrange("key", 0, -1, inserter(with_score, with_score.end())); + // + // This also applies to other commands with the *WITHSCORES* option, + // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrank(const StringView &key, const StringView &member); + + long long zrem(const StringView &key, const StringView &member); + + template + long long zrem(const StringView &key, Input first, Input last); + + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + OptionalDouble zscore(const StringView &key, const StringView &member); + + // There's no aggregation type parameter for single key overload, since these 3 types + // have the same effect. + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + // See *zinterstore* comment for how to use this method. + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + bool pfadd(const StringView &key, const StringView &element); + + template + bool pfadd(const StringView &key, Input first, Input last); + + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + void script_exists(Input first, Input last, Output output); + + template + void script_exists(std::initializer_list il, Output output) { + script_exists(il.begin(), il.end(), output); + } + + void script_flush(); + + void script_kill(); + + std::string script_load(const StringView &script); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Transaction commands. + void watch(const StringView &key); + + template + void watch(Input first, Input last); + + template + void watch(std::initializer_list il) { + watch(il.begin(), il.end()); + } + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first ,last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + class ConnectionPoolGuard { + public: + ConnectionPoolGuard(ConnectionPool &pool, + Connection &connection) : _pool(pool), _connection(connection) {} + + ~ConnectionPoolGuard() { + _pool.release(std::move(_connection)); + } + + private: + ConnectionPool &_pool; + Connection &_connection; + }; + + template + friend class QueuedRedis; + + friend class RedisCluster; + + // For internal use. + explicit Redis(const ConnectionSPtr &connection); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _command(Connection &connection, Cmd cmd, Args &&...args); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + // Pool Mode. + // Public constructors create a *Redis* instance with a pool. + // In this case, *_connection* is a null pointer, and is never used. + ConnectionPool _pool; + + // Single Connection Mode. + // Private constructor creats a *Redis* instance with a single connection. + // This is used when we create Transaction, Pipeline and Subscriber. + // In this case, *_pool* is empty, and is never used. + ConnectionSPtr _connection; +}; + +} + +} + +#include "redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.hpp b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.hpp new file mode 100644 index 000000000..3a227a6f1 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.hpp @@ -0,0 +1,1365 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_HPP + +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +template +auto Redis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (_connection) { + // Single Connection Mode. + // TODO: In this case, should we reconnect? + if (_connection->broken()) { + throw Error("Connection is broken"); + } + + return _command(*_connection, cmd, std::forward(args)...); + } else { + // Pool Mode, i.e. get connection from pool. + auto connection = _pool.fetch(); + + assert(!connection.broken()); + + ConnectionPoolGuard guard(_pool, connection); + + return _command(connection, cmd, std::forward(args)...); + } +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, ReplyUPtr>::type { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (first == last) { + throw Error("command: empty range"); + } + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +Result Redis::command(const StringView &cmd_name, Args &&...args) { + auto r = command(cmd_name, std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long Redis::del(Input first, Input last) { + if (first == last) { + throw Error("DEL: no key specified"); + } + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::exists(Input first, Input last) { + if (first == last) { + throw Error("EXISTS: no key specified"); + } + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool Redis::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool Redis::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +template +void Redis::keys(const StringView &pattern, Output output) { + auto reply = command(cmd::keys, pattern); + + reply::to_array(*reply, output); +} + +inline bool Redis::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool Redis::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void Redis::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long Redis::scan(long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::scan, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::scan(long long cursor, + const StringView &pattern, + Output output) { + return scan(cursor, pattern, 10, output); +} + +template +inline long long Redis::scan(long long cursor, + long long count, + Output output) { + return scan(cursor, "*", count, output); +} + +template +inline long long Redis::scan(long long cursor, + Output output) { + return scan(cursor, "*", 10, output); +} + +template +long long Redis::touch(Input first, Input last) { + if (first == last) { + throw Error("TOUCH: no key specified"); + } + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::unlink(Input first, Input last) { + if (first == last) { + throw Error("UNLINK: no key specified"); + } + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +inline long long Redis::wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); +} + +// STRING commands. + +template +long long Redis::bitop(BitOp op, const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("BITOP: no key specified"); + } + + auto reply = command(cmd::bitop_range, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::mget(Input first, Input last, Output output) { + if (first == last) { + throw Error("MGET: no key specified"); + } + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::mset(Input first, Input last) { + if (first == last) { + throw Error("MSET: no key specified"); + } + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool Redis::msetnx(Input first, Input last) { + if (first == last) { + throw Error("MSETNX: no key specified"); + } + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void Redis::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void Redis::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair Redis::blpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BLPOP: no key specified"); + } + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair Redis::brpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BRPOP: no key specified"); + } + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString Redis::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long Redis::lpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("LPUSH: no key specified"); + } + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long Redis::rpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("RPUSH: no key specified"); + } + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long Redis::hdel(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HDEL: no key specified"); + } + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmget(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("HMGET: no key specified"); + } + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmset(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HMSET: no key specified"); + } + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +inline void Redis::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long Redis::sadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SADD: no key specified"); + } + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sdiff(Input first, Input last, Output output) { + if (first == last) { + throw Error("SDIFF: no key specified"); + } + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sdiffstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SDIFFSTORE: no key specified"); + } + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sinter(Input first, Input last, Output output) { + if (first == last) { + throw Error("SINTER: no key specified"); + } + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sinterstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SINTERSTORE: no key specified"); + } + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void Redis::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long Redis::srem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SREM: no key specified"); + } + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void Redis::sunion(Input first, Input last, Output output) { + if (first == last) { + throw Error("SUNION: no key specified"); + } + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sunionstore(const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("SUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto Redis::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto Redis::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto Redis::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto Redis::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long Redis::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + if (first == last) { + throw Error("ZADD: no key specified"); + } + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long Redis::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZINTERSTORE: no key specified"); + } + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long Redis::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void Redis::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zrem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("ZREM: no key specified"); + } + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long Redis::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool Redis::pfadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("PFADD: no key specified"); + } + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::pfcount(Input first, Input last) { + if (first == last) { + throw Error("PFCOUNT: no key specified"); + } + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void Redis::pfmerge(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("PFMERGE: no key specified"); + } + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long Redis::geoadd(const StringView &key, + Input first, + Input last) { + if (first == last) { + throw Error("GEOADD: no key specified"); + } + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::geohash(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOHASH: no key specified"); + } + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::geopos(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOPOS: no key specified"); + } + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void Redis::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + auto reply = command(cmd::eval, script, keys, args); + + return reply::parse(*reply); +} + +template +void Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + auto reply = command(cmd::eval, script, keys, args); + + reply::to_array(*reply, output); +} + +template +Result Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + auto reply = command(cmd::evalsha, script, keys, args); + + return reply::parse(*reply); +} + +template +void Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + auto reply = command(cmd::evalsha, script, keys, args); + + reply::to_array(*reply, output); +} + +template +void Redis::script_exists(Input first, Input last, Output output) { + if (first == last) { + throw Error("SCRIPT EXISTS: no key specified"); + } + + auto reply = command(cmd::script_exists_range, first, last); + + reply::to_array(*reply, output); +} + +// Transaction commands. + +template +void Redis::watch(Input first, Input last) { + auto reply = command(cmd::watch_range, first, last); + + reply::parse(*reply); +} + +// Stream commands. + +template +long long Redis::xack(const StringView &key, const StringView &group, Input first, Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, const StringView &id, Input first, Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long Redis::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto Redis::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +ReplyUPtr Redis::_command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + auto reply = connection.recv(); + + return reply; +} + +template +inline ReplyUPtr Redis::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr Redis::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr Redis::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_HPP diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.h new file mode 100644 index 000000000..50a221367 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.h @@ -0,0 +1,1395 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H + +#include +#include +#include +#include +#include "shards_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "redis.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class RedisCluster { +public: + RedisCluster(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(pool_opts, connection_opts) {} + + // Construct RedisCluster with URI: + // "tcp://127.0.0.1" or "tcp://127.0.0.1:6379" + // Only need to specify one URI. + explicit RedisCluster(const std::string &uri); + + RedisCluster(const RedisCluster &) = delete; + RedisCluster& operator=(const RedisCluster &) = delete; + + RedisCluster(RedisCluster &&) = default; + RedisCluster& operator=(RedisCluster &&) = default; + + Redis redis(const StringView &hash_tag); + + Pipeline pipeline(const StringView &hash_tag); + + Transaction transaction(const StringView &hash_tag, bool piped = false); + + Subscriber subscriber(); + + template + auto command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // KEY commands. + + long long del(const StringView &key); + + template + long long del(Input first, Input last); + + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + OptionalString dump(const StringView &key); + + long long exists(const StringView &key); + + template + long long exists(Input first, Input last); + + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + bool expire(const StringView &key, long long timeout); + + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + bool expireat(const StringView &key, long long timestamp); + + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + bool persist(const StringView &key); + + bool pexpire(const StringView &key, long long timeout); + + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + bool pexpireat(const StringView &key, long long timestamp); + + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + long long pttl(const StringView &key); + + void rename(const StringView &key, const StringView &newkey); + + bool renamenx(const StringView &key, const StringView &newkey); + + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + long long touch(const StringView &key); + + template + long long touch(Input first, Input last); + + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + long long ttl(const StringView &key); + + std::string type(const StringView &key); + + long long unlink(const StringView &key); + + template + long long unlink(Input first, Input last); + + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + // STRING commands. + + long long append(const StringView &key, const StringView &str); + + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + long long decr(const StringView &key); + + long long decrby(const StringView &key, long long decrement); + + OptionalString get(const StringView &key); + + long long getbit(const StringView &key, long long offset); + + std::string getrange(const StringView &key, long long start, long long end); + + OptionalString getset(const StringView &key, const StringView &val); + + long long incr(const StringView &key); + + long long incrby(const StringView &key, long long increment); + + double incrbyfloat(const StringView &key, double increment); + + template + void mget(Input first, Input last, Output output); + + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + template + void mset(Input first, Input last); + + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + template + bool msetnx(Input first, Input last); + + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + void setex(const StringView &key, + long long ttl, + const StringView &val); + + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + bool setnx(const StringView &key, const StringView &val); + + long long setrange(const StringView &key, long long offset, const StringView &val); + + long long strlen(const StringView &key); + + // LIST commands. + + OptionalStringPair blpop(const StringView &key, long long timeout); + + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + OptionalStringPair brpop(const StringView &key, long long timeout); + + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + OptionalString lindex(const StringView &key, long long index); + + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + long long llen(const StringView &key); + + OptionalString lpop(const StringView &key); + + long long lpush(const StringView &key, const StringView &val); + + template + long long lpush(const StringView &key, Input first, Input last); + + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + long long lpushx(const StringView &key, const StringView &val); + + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + long long lrem(const StringView &key, long long count, const StringView &val); + + void lset(const StringView &key, long long index, const StringView &val); + + void ltrim(const StringView &key, long long start, long long stop); + + OptionalString rpop(const StringView &key); + + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + long long rpush(const StringView &key, const StringView &val); + + template + long long rpush(const StringView &key, Input first, Input last); + + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + long long hdel(const StringView &key, const StringView &field); + + template + long long hdel(const StringView &key, Input first, Input last); + + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + bool hexists(const StringView &key, const StringView &field); + + OptionalString hget(const StringView &key, const StringView &field); + + template + void hgetall(const StringView &key, Output output); + + long long hincrby(const StringView &key, const StringView &field, long long increment); + + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + template + void hkeys(const StringView &key, Output output); + + long long hlen(const StringView &key); + + template + void hmget(const StringView &key, Input first, Input last, Output output); + + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + template + void hmset(const StringView &key, Input first, Input last); + + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + bool hset(const StringView &key, const StringView &field, const StringView &val); + + bool hset(const StringView &key, const std::pair &item); + + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + bool hsetnx(const StringView &key, const std::pair &item); + + long long hstrlen(const StringView &key, const StringView &field); + + template + void hvals(const StringView &key, Output output); + + // SET commands. + + long long sadd(const StringView &key, const StringView &member); + + template + long long sadd(const StringView &key, Input first, Input last); + + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + long long scard(const StringView &key); + + template + void sdiff(Input first, Input last, Output output); + + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + long long sdiffstore(const StringView &destination, const StringView &key); + + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + void sinter(Input first, Input last, Output output); + + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + long long sinterstore(const StringView &destination, const StringView &key); + + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + bool sismember(const StringView &key, const StringView &member); + + template + void smembers(const StringView &key, Output output); + + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + OptionalString spop(const StringView &key); + + template + void spop(const StringView &key, long long count, Output output); + + OptionalString srandmember(const StringView &key); + + template + void srandmember(const StringView &key, long long count, Output output); + + long long srem(const StringView &key, const StringView &member); + + template + long long srem(const StringView &key, Input first, Input last); + + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + template + void sunion(Input first, Input last, Output output); + + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + long long sunionstore(const StringView &destination, const StringView &key); + + template + long long sunionstore(const StringView &destination, Input first, Input last); + + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + long long zcard(const StringView &key); + + template + long long zcount(const StringView &key, const Interval &interval); + + double zincrby(const StringView &key, double increment, const StringView &member); + + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + long long zlexcount(const StringView &key, const Interval &interval); + + Optional> zpopmax(const StringView &key); + + template + void zpopmax(const StringView &key, long long count, Output output); + + Optional> zpopmin(const StringView &key); + + template + void zpopmin(const StringView &key, long long count, Output output); + + // If *output* is an iterator of a container of string, + // we send *ZRANGE key start stop* command. + // If it's an iterator of a container of pair, + // we send *ZRANGE key start stop WITHSCORES* command. + // + // The following code sends *ZRANGE* without the *WITHSCORES* option: + // + // vector result; + // redis.zrange("key", 0, -1, back_inserter(result)); + // + // On the other hand, the following code sends command with *WITHSCORES* option: + // + // unordered_map with_score; + // redis.zrange("key", 0, -1, inserter(with_score, with_score.end())); + // + // This also applies to other commands with the *WITHSCORES* option, + // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrank(const StringView &key, const StringView &member); + + long long zrem(const StringView &key, const StringView &member); + + template + long long zrem(const StringView &key, Input first, Input last); + + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + OptionalDouble zscore(const StringView &key, const StringView &member); + + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + bool pfadd(const StringView &key, const StringView &element); + + template + bool pfadd(const StringView &key, Input first, Input last); + + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first, last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + class Command { + public: + explicit Command(const StringView &cmd_name) : _cmd_name(cmd_name) {} + + template + void operator()(Connection &connection, Args &&...args) const { + CmdArgs cmd_args; + cmd_args.append(_cmd_name, std::forward(args)...); + connection.send(cmd_args); + } + + private: + StringView _cmd_name; + }; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + ReplyUPtr _command(Cmd cmd, Connection &connection, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::true_type, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::false_type, Input &&first, Args &&...args); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _range_command(Cmd cmd, std::true_type, Input input, Args &&...args); + + template + ReplyUPtr _range_command(Cmd cmd, std::false_type, Input input, Args &&...args); + + void _asking(Connection &connection); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + ShardsPool _pool; +}; + +} + +} + +#include "redis_cluster.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.hpp b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.hpp new file mode 100644 index 000000000..61da3f062 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.hpp @@ -0,0 +1,1415 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP + +#include +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" +#include "shards_pool.h" + +namespace sw { + +namespace redis { + +template +auto RedisCluster::command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + return _command(cmd, + std::is_convertible::type, StringView>(), + std::forward(key), + std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type { + auto cmd = Command(cmd_name); + + return _generic_command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type { + auto r = command(cmd_name, std::forward(key), std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(key), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (first == last || std::next(first) == last) { + throw Error("command: invalid range"); + } + + const auto &key = *first; + ++first; + + auto cmd = [&key](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + cmd_args.append(key); + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long RedisCluster::del(Input first, Input last) { + if (first == last) { + throw Error("DEL: no key specified"); + } + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::exists(Input first, Input last) { + if (first == last) { + throw Error("EXISTS: no key specified"); + } + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool RedisCluster::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool RedisCluster::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +inline bool RedisCluster::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool RedisCluster::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void RedisCluster::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long RedisCluster::touch(Input first, Input last) { + if (first == last) { + throw Error("TOUCH: no key specified"); + } + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::unlink(Input first, Input last) { + if (first == last) { + throw Error("UNLINK: no key specified"); + } + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +// STRING commands. + +template +long long RedisCluster::bitop(BitOp op, const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("BITOP: no key specified"); + } + + auto reply = _command(cmd::bitop_range, destination, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::mget(Input first, Input last, Output output) { + if (first == last) { + throw Error("MGET: no key specified"); + } + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::mset(Input first, Input last) { + if (first == last) { + throw Error("MSET: no key specified"); + } + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool RedisCluster::msetnx(Input first, Input last) { + if (first == last) { + throw Error("MSETNX: no key specified"); + } + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void RedisCluster::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void RedisCluster::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair RedisCluster::blpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BLPOP: no key specified"); + } + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BRPOP: no key specified"); + } + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString RedisCluster::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long RedisCluster::lpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("LPUSH: no key specified"); + } + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long RedisCluster::rpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("RPUSH: no key specified"); + } + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long RedisCluster::hdel(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HDEL: no key specified"); + } + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmget(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("HMGET: no key specified"); + } + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmset(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HMSET: no key specified"); + } + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +inline void RedisCluster::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long RedisCluster::sadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SADD: no key specified"); + } + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sdiff(Input first, Input last, Output output) { + if (first == last) { + throw Error("SDIFF: no key specified"); + } + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sdiffstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SDIFFSTORE: no key specified"); + } + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sinter(Input first, Input last, Output output) { + if (first == last) { + throw Error("SINTER: no key specified"); + } + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sinterstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SINTERSTORE: no key specified"); + } + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::srem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SREM: no key specified"); + } + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void RedisCluster::sunion(Input first, Input last, Output output) { + if (first == last) { + throw Error("SUNION: no key specified"); + } + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sunionstore(const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("SUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto RedisCluster::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto RedisCluster::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long RedisCluster::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + if (first == last) { + throw Error("ZADD: no key specified"); + } + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZINTERSTORE: no key specified"); + } + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zrem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("ZREM: no key specified"); + } + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long RedisCluster::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool RedisCluster::pfadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("PFADD: no key specified"); + } + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::pfcount(Input first, Input last) { + if (first == last) { + throw Error("PFCOUNT: no key specified"); + } + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::pfmerge(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("PFMERGE: no key specified"); + } + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long RedisCluster::geoadd(const StringView &key, + Input first, + Input last) { + if (first == last) { + throw Error("GEOADD: no key specified"); + } + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::geohash(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOHASH: no key specified"); + } + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::geopos(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOPOS: no key specified"); + } + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, *keys.begin(), script, keys, args); + + return reply::parse(*reply); +} + +template +void RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, *keys.begin(), script, keys, args); + + reply::to_array(*reply, output); +} + +template +Result RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::evalsha, *keys.begin(), script, keys, args); + + return reply::parse(*reply); +} + +template +void RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = command(cmd::evalsha, *keys.begin(), script, keys, args); + + reply::to_array(*reply, output); +} + +// Stream commands. + +template +long long RedisCluster::xack(const StringView &key, + const StringView &group, + Input first, + Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto RedisCluster::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup, key, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = _command(cmd::xreadgroup_range, + first->first, + group, + consumer, + first, + last, + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup_block, + key, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = _command(cmd::xreadgroup_block_range, + first->first, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type { + return command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type { + auto k = std::to_string(std::forward(key)); + return command(cmd, k, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::true_type, const StringView &key, Args &&...args) { + return _command(cmd, key, key, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::false_type, Input &&first, Args &&...args) { + return _range_command(cmd, + std::is_convertible< + typename std::decay< + decltype(*std::declval())>::type, StringView>(), + std::forward(first), + std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::true_type, Input input, Args &&...args) { + return _command(cmd, *input, input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::false_type, Input input, Args &&...args) { + return _command(cmd, std::get<0>(*input), input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, Connection &connection, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + return connection.recv(); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, const StringView &key, Args &&...args) { + for (auto idx = 0; idx < 2; ++idx) { + try { + auto guarded_connection = _pool.fetch(key); + + return _command(cmd, guarded_connection.connection(), std::forward(args)...); + } catch (const IoError &err) { + // When master is down, one of its replicas will be promoted to be the new master. + // If we try to send command to the old master, we'll get an *IoError*. + // In this case, we need to update the slots mapping. + _pool.update(); + } catch (const ClosedError &err) { + // Node might be removed. + // 1. Get up-to-date slot mapping to check if the node still exists. + _pool.update(); + + // TODO: + // 2. If it's NOT exist, update slot mapping, and retry. + // 3. If it's still exist, that means the node is down, NOT removed, throw exception. + } catch (const MovedError &err) { + // Slot mapping has been changed, update it and try again. + _pool.update(); + } catch (const AskError &err) { + auto guarded_connection = _pool.fetch(err.node()); + auto &connection = guarded_connection.connection(); + + // 1. send ASKING command. + _asking(connection); + + // 2. resend last command. + try { + return _command(cmd, connection, std::forward(args)...); + } catch (const MovedError &err) { + throw Error("Slot migrating... ASKING node hasn't been set to IMPORTING state"); + } + } // For other exceptions, just throw it. + } + + // Possible failures: + // 1. Source node has already run 'CLUSTER SETSLOT xxx NODE xxx', + // while the destination node has NOT run it. + // In this case, client will be redirected by both nodes with MovedError. + // 2. Other failures... + throw Error("Failed to send command with key: " + std::string(key.data(), key.size())); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr RedisCluster::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/reply.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/reply.h new file mode 100644 index 000000000..b309de5bb --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/reply.h @@ -0,0 +1,363 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REPLY_H +#define SEWENEW_REDISPLUSPLUS_REPLY_H + +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +struct ReplyDeleter { + void operator()(redisReply *reply) const { + if (reply != nullptr) { + freeReplyObject(reply); + } + } +}; + +using ReplyUPtr = std::unique_ptr; + +namespace reply { + +template +struct ParseTag {}; + +template +inline T parse(redisReply &reply) { + return parse(ParseTag(), reply); +} + +void parse(ParseTag, redisReply &reply); + +std::string parse(ParseTag, redisReply &reply); + +long long parse(ParseTag, redisReply &reply); + +double parse(ParseTag, redisReply &reply); + +bool parse(ParseTag, redisReply &reply); + +template +Optional parse(ParseTag>, redisReply &reply); + +template +std::pair parse(ParseTag>, redisReply &reply); + +template +std::tuple parse(ParseTag>, redisReply &reply); + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template +long long parse_scan_reply(redisReply &reply, Output output); + +inline bool is_error(redisReply &reply) { + return reply.type == REDIS_REPLY_ERROR; +} + +inline bool is_nil(redisReply &reply) { + return reply.type == REDIS_REPLY_NIL; +} + +inline bool is_string(redisReply &reply) { + return reply.type == REDIS_REPLY_STRING; +} + +inline bool is_status(redisReply &reply) { + return reply.type == REDIS_REPLY_STATUS; +} + +inline bool is_integer(redisReply &reply) { + return reply.type == REDIS_REPLY_INTEGER; +} + +inline bool is_array(redisReply &reply) { + return reply.type == REDIS_REPLY_ARRAY; +} + +std::string to_status(redisReply &reply); + +template +void to_array(redisReply &reply, Output output); + +// Rewrite set reply to bool type +void rewrite_set_reply(redisReply &reply); + +// Rewrite georadius reply to OptionalLongLong type +void rewrite_georadius_reply(redisReply &reply); + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple; + +} + +// Inline implementations. + +namespace reply { + +namespace detail { + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.element == nullptr) { + // Empty array. + return; + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + auto *sub_reply = reply.element[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null array element reply"); + } + + *output = parse::type>(*sub_reply); + + ++output; + } +} + +bool is_flat_array(redisReply &reply); + +template +void to_flat_array(redisReply &reply, Output output) { + if (reply.element == nullptr) { + // Empty array. + return; + } + + if (reply.elements % 2 != 0) { + throw ProtoError("Not string pair array reply"); + } + + for (std::size_t idx = 0; idx != reply.elements; idx += 2) { + auto *key_reply = reply.element[idx]; + auto *val_reply = reply.element[idx + 1]; + if (key_reply == nullptr || val_reply == nullptr) { + throw ProtoError("Null string array reply"); + } + + using Pair = typename IterType::type; + using FirstType = typename std::decay::type; + using SecondType = typename std::decay::type; + *output = std::make_pair(parse(*key_reply), + parse(*val_reply)); + + ++output; + } +} + +template +void to_array(std::true_type, redisReply &reply, Output output) { + if (is_flat_array(reply)) { + to_flat_array(reply, output); + } else { + to_array(reply, output); + } +} + +template +void to_array(std::false_type, redisReply &reply, Output output) { + to_array(reply, output); +} + +template +std::tuple parse_tuple(redisReply **reply, std::size_t idx) { + assert(reply != nullptr); + + auto *sub_reply = reply[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null reply"); + } + + return std::make_tuple(parse(*sub_reply)); +} + +template +auto parse_tuple(redisReply **reply, std::size_t idx) -> + typename std::enable_if>::type { + assert(reply != nullptr); + + return std::tuple_cat(parse_tuple(reply, idx), + parse_tuple(reply, idx + 1)); +} + +} + +template +Optional parse(ParseTag>, redisReply &reply) { + if (reply::is_nil(reply)) { + return {}; + } + + return Optional(parse(reply)); +} + +template +std::pair parse(ParseTag>, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != 2) { + throw ProtoError("NOT key-value PAIR reply"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null PAIR reply"); + } + + auto *first = reply.element[0]; + auto *second = reply.element[1]; + if (first == nullptr || second == nullptr) { + throw ProtoError("Null pair reply"); + } + + return std::make_pair(parse::type>(*first), + parse::type>(*second)); +} + +template +std::tuple parse(ParseTag>, redisReply &reply) { + constexpr auto size = sizeof...(Args); + + static_assert(size > 0, "DO NOT support parsing tuple with 0 element"); + + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != size) { + throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null TUPLE reply"); + } + + return detail::parse_tuple(reply.element, 0); +} + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::back_inserter(container)); + + return container; +} + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::inserter(container, container.end())); + + return container; +} + +template +long long parse_scan_reply(redisReply &reply, Output output) { + if (reply.elements != 2 || reply.element == nullptr) { + throw ProtoError("Invalid scan reply"); + } + + auto *cursor_reply = reply.element[0]; + auto *data_reply = reply.element[1]; + if (cursor_reply == nullptr || data_reply == nullptr) { + throw ProtoError("Invalid cursor reply or data reply"); + } + + auto cursor_str = reply::parse(*cursor_reply); + auto new_cursor = 0; + try { + new_cursor = std::stoll(cursor_str); + } catch (const std::exception &e) { + throw ProtoError("Invalid cursor reply: " + cursor_str); + } + + reply::to_array(*data_reply, output); + + return new_cursor; +} + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + detail::to_array(typename IsKvPairIter::type(), reply, output); +} + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple { + if (!is_array(reply) || reply.elements != 4) { + throw ProtoError("expect array reply with 4 elements"); + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + if (reply.element[idx] == nullptr) { + throw ProtoError("null array reply"); + } + } + + auto num = parse(*(reply.element[0])); + auto start = parse(*(reply.element[1])); + auto end = parse(*(reply.element[2])); + + auto &entry_reply = *(reply.element[3]); + if (!is_nil(entry_reply)) { + to_array(entry_reply, output); + } + + return std::make_tuple(num, std::move(start), std::move(end)); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REPLY_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/sentinel.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/sentinel.h new file mode 100644 index 000000000..e80d1e56a --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/sentinel.h @@ -0,0 +1,138 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SENTINEL_H +#define SEWENEW_REDISPLUSPLUS_SENTINEL_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "shards.h" +#include "reply.h" + +namespace sw { + +namespace redis { + +struct SentinelOptions { + std::vector> nodes; + + std::string password; + + bool keep_alive = true; + + std::chrono::milliseconds connect_timeout{100}; + + std::chrono::milliseconds socket_timeout{100}; + + std::chrono::milliseconds retry_interval{100}; + + std::size_t max_retry = 2; +}; + +class Sentinel { +public: + explicit Sentinel(const SentinelOptions &sentinel_opts); + + Sentinel(const Sentinel &) = delete; + Sentinel& operator=(const Sentinel &) = delete; + + Sentinel(Sentinel &&) = delete; + Sentinel& operator=(Sentinel &&) = delete; + + ~Sentinel() = default; + +private: + Connection master(const std::string &master_name, const ConnectionOptions &opts); + + Connection slave(const std::string &master_name, const ConnectionOptions &opts); + + class Iterator; + + friend class SimpleSentinel; + + std::list _parse_options(const SentinelOptions &opts) const; + + Optional _get_master_addr_by_name(Connection &connection, const StringView &name); + + std::vector _get_slave_addr_by_name(Connection &connection, const StringView &name); + + Connection _connect_redis(const Node &node, ConnectionOptions opts); + + Role _get_role(Connection &connection); + + std::vector _parse_slave_info(redisReply &reply) const; + + std::list _healthy_sentinels; + + std::list _broken_sentinels; + + SentinelOptions _sentinel_opts; + + std::mutex _mutex; +}; + +class SimpleSentinel { +public: + SimpleSentinel(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role); + + SimpleSentinel() = default; + + SimpleSentinel(const SimpleSentinel &) = default; + SimpleSentinel& operator=(const SimpleSentinel &) = default; + + SimpleSentinel(SimpleSentinel &&) = default; + SimpleSentinel& operator=(SimpleSentinel &&) = default; + + ~SimpleSentinel() = default; + + explicit operator bool() const { + return bool(_sentinel); + } + + Connection create(const ConnectionOptions &opts); + +private: + std::shared_ptr _sentinel; + + std::string _master_name; + + Role _role = Role::MASTER; +}; + +class StopIterError : public Error { +public: + StopIterError() : Error("StopIterError") {} + + StopIterError(const StopIterError &) = default; + StopIterError& operator=(const StopIterError &) = default; + + StopIterError(StopIterError &&) = default; + StopIterError& operator=(StopIterError &&) = default; + + virtual ~StopIterError() = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SENTINEL_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards.h new file mode 100644 index 000000000..a0593acbc --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards.h @@ -0,0 +1,115 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_H + +#include +#include +#include "errors.h" + +namespace sw { + +namespace redis { + +using Slot = std::size_t; + +struct SlotRange { + Slot min; + Slot max; +}; + +inline bool operator<(const SlotRange &lhs, const SlotRange &rhs) { + return lhs.max < rhs.max; +} + +struct Node { + std::string host; + int port; +}; + +inline bool operator==(const Node &lhs, const Node &rhs) { + return lhs.host == rhs.host && lhs.port == rhs.port; +} + +struct NodeHash { + std::size_t operator()(const Node &node) const noexcept { + auto host_hash = std::hash{}(node.host); + auto port_hash = std::hash{}(node.port); + return host_hash ^ (port_hash << 1); + } +}; + +using Shards = std::map; + +class RedirectionError : public ReplyError { +public: + RedirectionError(const std::string &msg); + + RedirectionError(const RedirectionError &) = default; + RedirectionError& operator=(const RedirectionError &) = default; + + RedirectionError(RedirectionError &&) = default; + RedirectionError& operator=(RedirectionError &&) = default; + + virtual ~RedirectionError() = default; + + Slot slot() const { + return _slot; + } + + const Node& node() const { + return _node; + } + +private: + std::pair _parse_error(const std::string &msg) const; + + Slot _slot = 0; + Node _node; +}; + +class MovedError : public RedirectionError { +public: + explicit MovedError(const std::string &msg) : RedirectionError(msg) {} + + MovedError(const MovedError &) = default; + MovedError& operator=(const MovedError &) = default; + + MovedError(MovedError &&) = default; + MovedError& operator=(MovedError &&) = default; + + virtual ~MovedError() = default; +}; + +class AskError : public RedirectionError { +public: + explicit AskError(const std::string &msg) : RedirectionError(msg) {} + + AskError(const AskError &) = default; + AskError& operator=(const AskError &) = default; + + AskError(AskError &&) = default; + AskError& operator=(AskError &&) = default; + + virtual ~AskError() = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards_pool.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards_pool.h new file mode 100644 index 000000000..1184806e9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards_pool.h @@ -0,0 +1,137 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H + +#include +#include +#include +#include +#include +#include "reply.h" +#include "connection_pool.h" +#include "shards.h" + +namespace sw { + +namespace redis { + +using ConnectionPoolSPtr = std::shared_ptr; + +class GuardedConnection { +public: + GuardedConnection(const ConnectionPoolSPtr &pool) : _pool(pool), + _connection(_pool->fetch()) { + assert(!_connection.broken()); + } + + GuardedConnection(const GuardedConnection &) = delete; + GuardedConnection& operator=(const GuardedConnection &) = delete; + + GuardedConnection(GuardedConnection &&) = default; + GuardedConnection& operator=(GuardedConnection &&) = default; + + ~GuardedConnection() { + _pool->release(std::move(_connection)); + } + + Connection& connection() { + return _connection; + } + +private: + ConnectionPoolSPtr _pool; + Connection _connection; +}; + +class ShardsPool { +public: + ShardsPool() = default; + + ShardsPool(const ShardsPool &that) = delete; + ShardsPool& operator=(const ShardsPool &that) = delete; + + ShardsPool(ShardsPool &&that); + ShardsPool& operator=(ShardsPool &&that); + + ~ShardsPool() = default; + + ShardsPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + // Fetch a connection by key. + GuardedConnection fetch(const StringView &key); + + // Randomly pick a connection. + GuardedConnection fetch(); + + // Fetch a connection by node. + GuardedConnection fetch(const Node &node); + + void update(); + + ConnectionOptions connection_options(const StringView &key); + + ConnectionOptions connection_options(); + +private: + void _move(ShardsPool &&that); + + void _init_pool(const Shards &shards); + + Shards _cluster_slots(Connection &connection) const; + + ReplyUPtr _cluster_slots_command(Connection &connection) const; + + Shards _parse_reply(redisReply &reply) const; + + std::pair _parse_slot_info(redisReply &reply) const; + + // Get slot by key. + std::size_t _slot(const StringView &key) const; + + // Randomly pick a slot. + std::size_t _slot() const; + + ConnectionPoolSPtr& _get_pool(Slot slot); + + GuardedConnection _fetch(Slot slot); + + ConnectionOptions _connection_options(Slot slot); + + using NodeMap = std::unordered_map; + + NodeMap::iterator _add_node(const Node &node); + + ConnectionPoolOptions _pool_opts; + + ConnectionOptions _connection_opts; + + Shards _shards; + + NodeMap _pools; + + std::mutex _mutex; + + static const std::size_t SHARDS = 16383; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/subscriber.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/subscriber.h new file mode 100644 index 000000000..8b7c5cfb4 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/subscriber.h @@ -0,0 +1,231 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H +#define SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H + +#include +#include +#include +#include "connection.h" +#include "reply.h" +#include "command.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +// @NOTE: Subscriber is NOT thread-safe. +// Subscriber uses callbacks to handle messages. There are 6 kinds of messages: +// 1) MESSAGE: message sent to a channel. +// 2) PMESSAGE: message sent to channels of a given pattern. +// 3) SUBSCRIBE: meta message sent when we successfully subscribe to a channel. +// 4) UNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel. +// 5) PSUBSCRIBE: meta message sent when we successfully subscribe to a channel pattern. +// 6) PUNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel pattern. +// +// Use Subscriber::on_message(MsgCallback) to set the callback function for message of +// *MESSAGE* type, and the callback interface is: +// void (std::string channel, std::string msg) +// +// Use Subscriber::on_pmessage(PatternMsgCallback) to set the callback function for message of +// *PMESSAGE* type, and the callback interface is: +// void (std::string pattern, std::string channel, std::string msg) +// +// Messages of other types are called *META MESSAGE*, they have the same callback interface. +// Use Subscriber::on_meta(MetaCallback) to set the callback function: +// void (Subscriber::MsgType type, OptionalString channel, long long num) +// +// NOTE: If we haven't subscribe/psubscribe to any channel/pattern, and try to +// unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all +// channels/patterns, *channel* will be null. So the second parameter of meta callback +// is of type *OptionalString*. +// +// All these callback interfaces pass std::string by value, and you can take their ownership +// (i.e. std::move) safely. +// +// If you don't set callback for a specific kind of message, Subscriber::consume() will +// receive the message, and ignore it, i.e. no callback will be called. +class Subscriber { +public: + Subscriber(const Subscriber &) = delete; + Subscriber& operator=(const Subscriber &) = delete; + + Subscriber(Subscriber &&) = default; + Subscriber& operator=(Subscriber &&) = default; + + ~Subscriber() = default; + + enum class MsgType { + SUBSCRIBE, + UNSUBSCRIBE, + PSUBSCRIBE, + PUNSUBSCRIBE, + MESSAGE, + PMESSAGE + }; + + template + void on_message(MsgCb msg_callback); + + template + void on_pmessage(PMsgCb pmsg_callback); + + template + void on_meta(MetaCb meta_callback); + + void subscribe(const StringView &channel); + + template + void subscribe(Input first, Input last); + + template + void subscribe(std::initializer_list channels) { + subscribe(channels.begin(), channels.end()); + } + + void unsubscribe(); + + void unsubscribe(const StringView &channel); + + template + void unsubscribe(Input first, Input last); + + template + void unsubscribe(std::initializer_list channels) { + unsubscribe(channels.begin(), channels.end()); + } + + void psubscribe(const StringView &pattern); + + template + void psubscribe(Input first, Input last); + + template + void psubscribe(std::initializer_list channels) { + psubscribe(channels.begin(), channels.end()); + } + + void punsubscribe(); + + void punsubscribe(const StringView &channel); + + template + void punsubscribe(Input first, Input last); + + template + void punsubscribe(std::initializer_list channels) { + punsubscribe(channels.begin(), channels.end()); + } + + void consume(); + +private: + friend class Redis; + + friend class RedisCluster; + + explicit Subscriber(Connection connection); + + MsgType _msg_type(redisReply *reply) const; + + void _check_connection(); + + void _handle_message(redisReply &reply); + + void _handle_pmessage(redisReply &reply); + + void _handle_meta(MsgType type, redisReply &reply); + + using MsgCallback = std::function; + + using PatternMsgCallback = std::function; + + using MetaCallback = std::function; + + using TypeIndex = std::unordered_map; + static const TypeIndex _msg_type_index; + + Connection _connection; + + MsgCallback _msg_callback = nullptr; + + PatternMsgCallback _pmsg_callback = nullptr; + + MetaCallback _meta_callback = nullptr; +}; + +template +void Subscriber::on_message(MsgCb msg_callback) { + _msg_callback = msg_callback; +} + +template +void Subscriber::on_pmessage(PMsgCb pmsg_callback) { + _pmsg_callback = pmsg_callback; +} + +template +void Subscriber::on_meta(MetaCb meta_callback) { + _meta_callback = meta_callback; +} + +template +void Subscriber::subscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::subscribe_range(_connection, first, last); +} + +template +void Subscriber::unsubscribe(Input first, Input last) { + _check_connection(); + + cmd::unsubscribe_range(_connection, first, last); +} + +template +void Subscriber::psubscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::psubscribe_range(_connection, first, last); +} + +template +void Subscriber::punsubscribe(Input first, Input last) { + _check_connection(); + + cmd::punsubscribe_range(_connection, first, last); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/transaction.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/transaction.h new file mode 100644 index 000000000..f19f24889 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/transaction.h @@ -0,0 +1,77 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TRANSACTION_H +#define SEWENEW_REDISPLUSPLUS_TRANSACTION_H + +#include +#include +#include "connection.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +class TransactionImpl { +public: + explicit TransactionImpl(bool piped) : _piped(piped) {} + + template + void command(Connection &connection, Cmd cmd, Args &&...args); + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t cmd_num); + +private: + void _open_transaction(Connection &connection); + + void _close_transaction(); + + void _get_queued_reply(Connection &connection); + + void _get_queued_replies(Connection &connection, std::size_t cmd_num); + + std::vector _exec(Connection &connection); + + void _discard(Connection &connection); + + bool _in_transaction = false; + + bool _piped; +}; + +template +void TransactionImpl::command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + if (!_in_transaction) { + _open_transaction(connection); + } + + cmd(connection, std::forward(args)...); + + if (!_piped) { + _get_queued_reply(connection); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TRANSACTION_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/utils.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/utils.h new file mode 100644 index 000000000..e29e64e14 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/utils.h @@ -0,0 +1,269 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_UTILS_H +#define SEWENEW_REDISPLUSPLUS_UTILS_H + +#include +#include +#include + +namespace sw { + +namespace redis { + +// By now, not all compilers support std::string_view, +// so we make our own implementation. +class StringView { +public: + constexpr StringView() noexcept = default; + + constexpr StringView(const char *data, std::size_t size) : _data(data), _size(size) {} + + StringView(const char *data) : _data(data), _size(std::strlen(data)) {} + + StringView(const std::string &str) : _data(str.data()), _size(str.size()) {} + + constexpr StringView(const StringView &) noexcept = default; + + StringView& operator=(const StringView &) noexcept = default; + + constexpr const char* data() const noexcept { + return _data; + } + + constexpr std::size_t size() const noexcept { + return _size; + } + +private: + const char *_data = nullptr; + std::size_t _size = 0; +}; + +template +class Optional { +public: + Optional() = default; + + Optional(const Optional &) = default; + Optional& operator=(const Optional &) = default; + + Optional(Optional &&) = default; + Optional& operator=(Optional &&) = default; + + ~Optional() = default; + + template + explicit Optional(Args &&...args) : _value(true, T(std::forward(args)...)) {} + + explicit operator bool() const { + return _value.first; + } + + T& value() { + return _value.second; + } + + const T& value() const { + return _value.second; + } + + T* operator->() { + return &(_value.second); + } + + const T* operator->() const { + return &(_value.second); + } + + T& operator*() { + return _value.second; + } + + const T& operator*() const { + return _value.second; + } + +private: + std::pair _value; +}; + +using OptionalString = Optional; + +using OptionalLongLong = Optional; + +using OptionalDouble = Optional; + +using OptionalStringPair = Optional>; + +template +struct IsKvPair : std::false_type {}; + +template +struct IsKvPair> : std::true_type {}; + +template +using Void = void; + +template > +struct IsInserter : std::false_type {}; + +template +//struct IsInserter> : std::true_type {}; +struct IsInserter::value>::type> + : std::true_type {}; + +template > +struct IterType { + using type = typename std::iterator_traits::value_type; +}; + +template +//struct IterType> { +struct IterType::value>::type> { + typename std::enable_if::value>::type> { + using type = typename std::decay::type; +}; + +template > +struct IsIter : std::false_type {}; + +template +struct IsIter::value>::type> : std::true_type {}; + +template +//struct IsIter::iterator_category>> +struct IsIter::value_type>::value>::type> + : std::integral_constant::value> {}; + +template +struct IsKvPairIter : IsKvPair::type> {}; + +template +struct TupleWithType : std::false_type {}; + +template +struct TupleWithType> : std::false_type {}; + +template +struct TupleWithType> : TupleWithType> {}; + +template +struct TupleWithType> : std::true_type {}; + +template +struct IndexSequence {}; + +template +struct MakeIndexSequence : MakeIndexSequence {}; + +template +struct MakeIndexSequence<0, Is...> : IndexSequence {}; + +// NthType and NthValue are taken from +// https://stackoverflow.com/questions/14261183 +template +struct NthType {}; + +template +struct NthType<0, Arg, Args...> { + using type = Arg; +}; + +template +struct NthType { + using type = typename NthType::type; +}; + +template +struct LastType { + using type = typename NthType::type; +}; + +struct InvalidLastType {}; + +template <> +struct LastType<> { + using type = InvalidLastType; +}; + +template +auto NthValue(Arg &&arg, Args &&...) + -> typename std::enable_if<(I == 0), decltype(std::forward(arg))>::type { + return std::forward(arg); +} + +template +auto NthValue(Arg &&, Args &&...args) + -> typename std::enable_if<(I > 0), + decltype(std::forward::type>( + std::declval::type>()))>::type { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template +auto LastValue(Args &&...args) + -> decltype(std::forward::type>( + std::declval::type>())) { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template > +struct HasPushBack : std::false_type {}; + +template +struct HasPushBack().push_back(std::declval()) + )>::value>::type> : std::true_type {}; + +template > +struct HasInsert : std::false_type {}; + +template +struct HasInsert().insert(std::declval(), + std::declval())), + typename T::iterator>::value>::type> : std::true_type {}; + +template +struct IsSequenceContainer + : std::integral_constant::value + && !std::is_same::type, std::string>::value> {}; + +template +struct IsAssociativeContainer + : std::integral_constant::value && !HasPushBack::value> {}; + +uint16_t crc16(const char *buf, int len); + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_UTILS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/command.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command.cpp new file mode 100644 index 000000000..bb1afb2a9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command.cpp @@ -0,0 +1,376 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "command.h" +#include + +namespace sw { + +namespace redis { + +namespace cmd { + +// KEY commands. + +void restore(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + bool replace) { + CmdArgs args; + args << "RESTORE" << key << ttl << val; + + if (replace) { + args << "REPLACE"; + } + + connection.send(args); +} + +// STRING commands. + +void bitop(Connection &connection, + BitOp op, + const StringView &destination, + const StringView &key) { + CmdArgs args; + + detail::set_bitop(args, op); + + args << destination << key; + + connection.send(args); +} + +void set(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + UpdateType type) { + CmdArgs args; + args << "SET" << key << val; + + if (ttl > 0) { + args << "PX" << ttl; + } + + detail::set_update_type(args, type); + + connection.send(args); +} + +// LIST commands. + +void linsert(Connection &connection, + const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val) { + std::string pos; + switch (position) { + case InsertPosition::BEFORE: + pos = "BEFORE"; + break; + + case InsertPosition::AFTER: + pos = "AFTER"; + break; + + default: + assert(false); + } + + connection.send("LINSERT %b %s %b %b", + key.data(), key.size(), + pos.c_str(), + pivot.data(), pivot.size(), + val.data(), val.size()); +} + +// GEO commands. + +void geodist(Connection &connection, + const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit) { + CmdArgs args; + args << "GEODIST" << key << member1 << member2; + + detail::set_geo_unit(args, unit); + + connection.send(args); +} + +void georadius_store(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + CmdArgs args; + args << "GEORADIUS" << key << loc.first << loc.second; + + detail::set_georadius_store_parameters(args, + radius, + unit, + destination, + store_dist, + count); + + connection.send(args); +} + +void georadius(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + CmdArgs args; + args << "GEORADIUS" << key << loc.first << loc.second; + + detail::set_georadius_parameters(args, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + + connection.send(args); +} + +void georadiusbymember(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + CmdArgs args; + args << "GEORADIUSBYMEMBER" << key << member; + + detail::set_georadius_parameters(args, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + + connection.send(args); +} + +void georadiusbymember_store(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + CmdArgs args; + args << "GEORADIUSBYMEMBER" << key << member; + + detail::set_georadius_store_parameters(args, + radius, + unit, + destination, + store_dist, + count); + + connection.send(args); +} + +// Stream commands. + +void xtrim(Connection &connection, const StringView &key, long long count, bool approx) { + CmdArgs args; + args << "XTRIM" << key << "MAXLEN"; + + if (approx) { + args << "~"; + } + + args << count; + + connection.send(args); +} + +namespace detail { + +void set_bitop(CmdArgs &args, BitOp op) { + args << "BITOP"; + + switch (op) { + case BitOp::AND: + args << "AND"; + break; + + case BitOp::OR: + args << "OR"; + break; + + case BitOp::XOR: + args << "XOR"; + break; + + case BitOp::NOT: + args << "NOT"; + break; + + default: + throw Error("Unknown bit operations"); + } +} + +void set_update_type(CmdArgs &args, UpdateType type) { + switch (type) { + case UpdateType::EXIST: + args << "XX"; + break; + + case UpdateType::NOT_EXIST: + args << "NX"; + break; + + case UpdateType::ALWAYS: + // Do nothing. + break; + + default: + throw Error("Unknown update type"); + } +} + +void set_aggregation_type(CmdArgs &args, Aggregation aggr) { + args << "AGGREGATE"; + + switch (aggr) { + case Aggregation::SUM: + args << "SUM"; + break; + + case Aggregation::MIN: + args << "MIN"; + break; + + case Aggregation::MAX: + args << "MAX"; + break; + + default: + throw Error("Unknown aggregation type"); + } +} + +void set_geo_unit(CmdArgs &args, GeoUnit unit) { + switch (unit) { + case GeoUnit::M: + args << "m"; + break; + + case GeoUnit::KM: + args << "km"; + break; + + case GeoUnit::MI: + args << "mi"; + break; + + case GeoUnit::FT: + args << "ft"; + break; + + default: + throw Error("Unknown geo unit type"); + break; + } +} + +void set_georadius_store_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + args << radius; + + detail::set_geo_unit(args, unit); + + args << "COUNT" << count; + + if (store_dist) { + args << "STOREDIST"; + } else { + args << "STORE"; + } + + args << destination; +} + +void set_georadius_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + args << radius; + + detail::set_geo_unit(args, unit); + + if (with_coord) { + args << "WITHCOORD"; + } + + if (with_dist) { + args << "WITHDIST"; + } + + if (with_hash) { + args << "WITHHASH"; + } + + args << "COUNT" << count; + + if (asc) { + args << "ASC"; + } else { + args << "DESC"; + } +} + +} + +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/command.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command.h new file mode 100644 index 000000000..3a4b24c5e --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command.h @@ -0,0 +1,2233 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_H + +#include +#include +#include +#include +#include "connection.h" +#include "command_options.h" +#include "command_args.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace cmd { + +// CONNECTION command. +inline void auth(Connection &connection, const StringView &password) { + connection.send("AUTH %b", password.data(), password.size()); +} + +inline void echo(Connection &connection, const StringView &msg) { + connection.send("ECHO %b", msg.data(), msg.size()); +} + +inline void ping(Connection &connection) { + connection.send("PING"); +} + +inline void quit(Connection &connection) { + connection.send("QUIT"); +} + +inline void ping(Connection &connection, const StringView &msg) { + // If *msg* is empty, Redis returns am empty reply of REDIS_REPLY_STRING type. + connection.send("PING %b", msg.data(), msg.size()); +} + +inline void select(Connection &connection, long long idx) { + connection.send("SELECT %lld", idx); +} + +inline void swapdb(Connection &connection, long long idx1, long long idx2) { + connection.send("SWAPDB %lld %lld", idx1, idx2); +} + +// SERVER commands. + +inline void bgrewriteaof(Connection &connection) { + connection.send("BGREWRITEAOF"); +} + +inline void bgsave(Connection &connection) { + connection.send("BGSAVE"); +} + +inline void dbsize(Connection &connection) { + connection.send("DBSIZE"); +} + +inline void flushall(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHALL ASYNC"); + } else { + connection.send("FLUSHALL"); + } +} + +inline void flushdb(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHDB ASYNC"); + } else { + connection.send("FLUSHDB"); + } +} + +inline void info(Connection &connection) { + connection.send("INFO"); +} + +inline void info(Connection &connection, const StringView §ion) { + connection.send("INFO %b", section.data(), section.size()); +} + +inline void lastsave(Connection &connection) { + connection.send("LASTSAVE"); +} + +inline void save(Connection &connection) { + connection.send("SAVE"); +} + +// KEY commands. + +inline void del(Connection &connection, const StringView &key) { + connection.send("DEL %b", key.data(), key.size()); +} + +template +inline void del_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "DEL" << std::make_pair(first, last); + + connection.send(args); +} + +inline void dump(Connection &connection, const StringView &key) { + connection.send("DUMP %b", key.data(), key.size()); +} + +inline void exists(Connection &connection, const StringView &key) { + connection.send("EXISTS %b", key.data(), key.size()); +} + +template +inline void exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void expire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("EXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void expireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("EXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void keys(Connection &connection, const StringView &pattern) { + connection.send("KEYS %b", pattern.data(), pattern.size()); +} + +inline void move(Connection &connection, const StringView &key, long long db) { + connection.send("MOVE %b %lld", + key.data(), key.size(), + db); +} + +inline void persist(Connection &connection, const StringView &key) { + connection.send("PERSIST %b", key.data(), key.size()); +} + +inline void pexpire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("PEXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void pexpireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("PEXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void pttl(Connection &connection, const StringView &key) { + connection.send("PTTL %b", key.data(), key.size()); +} + +inline void randomkey(Connection &connection) { + connection.send("RANDOMKEY"); +} + +inline void rename(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAME %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +inline void renamenx(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAMENX %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +void restore(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + bool replace); + +inline void scan(Connection &connection, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SCAN %lld MATCH %b COUNT %lld", + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void touch(Connection &connection, const StringView &key) { + connection.send("TOUCH %b", key.data(), key.size()); +} + +template +inline void touch_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "TOUCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void ttl(Connection &connection, const StringView &key) { + connection.send("TTL %b", key.data(), key.size()); +} + +inline void type(Connection &connection, const StringView &key) { + connection.send("TYPE %b", key.data(), key.size()); +} + +inline void unlink(Connection &connection, const StringView &key) { + connection.send("UNLINK %b", key.data(), key.size()); +} + +template +inline void unlink_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "UNLINK" << std::make_pair(first, last); + + connection.send(args); +} + +inline void wait(Connection &connection, long long numslave, long long timeout) { + connection.send("WAIT %lld %lld", numslave, timeout); +} + +// STRING commands. + +inline void append(Connection &connection, const StringView &key, const StringView &str) { + connection.send("APPEND %b %b", + key.data(), key.size(), + str.data(), str.size()); +} + +inline void bitcount(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("BITCOUNT %b %lld %lld", + key.data(), key.size(), + start, end); +} + +void bitop(Connection &connection, + BitOp op, + const StringView &destination, + const StringView &key); + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last); + +inline void bitpos(Connection &connection, + const StringView &key, + long long bit, + long long start, + long long end) { + connection.send("BITPOS %b %lld %lld %lld", + key.data(), key.size(), + bit, + start, + end); +} + +inline void decr(Connection &connection, const StringView &key) { + connection.send("DECR %b", key.data(), key.size()); +} + +inline void decrby(Connection &connection, const StringView &key, long long decrement) { + connection.send("DECRBY %b %lld", + key.data(), key.size(), + decrement); +} + +inline void get(Connection &connection, const StringView &key) { + connection.send("GET %b", + key.data(), key.size()); +} + +inline void getbit(Connection &connection, const StringView &key, long long offset) { + connection.send("GETBIT %b %lld", + key.data(), key.size(), + offset); +} + +inline void getrange(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("GETRANGE %b %lld %lld", + key.data(), key.size(), + start, + end); +} + +inline void getset(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("GETSET %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void incr(Connection &connection, const StringView &key) { + connection.send("INCR %b", key.data(), key.size()); +} + +inline void incrby(Connection &connection, const StringView &key, long long increment) { + connection.send("INCRBY %b %lld", + key.data(), key.size(), + increment); +} + +inline void incrbyfloat(Connection &connection, const StringView &key, double increment) { + connection.send("INCRBYFLOAT %b %f", + key.data(), key.size(), + increment); +} + +template +inline void mget(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MGET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void mset(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void msetnx(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSETNX" << std::make_pair(first, last); + + connection.send(args); +} + +inline void psetex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("PSETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +void set(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + UpdateType type); + +inline void setex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("SETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +inline void setnx(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("SETNX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void setrange(Connection &connection, + const StringView &key, + long long offset, + const StringView &val) { + connection.send("SETRANGE %b %lld %b", + key.data(), key.size(), + offset, + val.data(), val.size()); +} + +inline void strlen(Connection &connection, const StringView &key) { + connection.send("STRLEN %b", key.data(), key.size()); +} + +// LIST commands. + +inline void blpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BLPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void blpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BLPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BRPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void brpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BRPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpoplpush(Connection &connection, + const StringView &source, + const StringView &destination, + long long timeout) { + connection.send("BRPOPLPUSH %b %b %lld", + source.data(), source.size(), + destination.data(), destination.size(), + timeout); +} + +inline void lindex(Connection &connection, const StringView &key, long long index) { + connection.send("LINDEX %b %lld", + key.data(), key.size(), + index); +} + +void linsert(Connection &connection, + const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + +inline void llen(Connection &connection, + const StringView &key) { + connection.send("LLEN %b", key.data(), key.size()); +} + +inline void lpop(Connection &connection, const StringView &key) { + connection.send("LPOP %b", + key.data(), key.size()); +} + +inline void lpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void lpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "LPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void lpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void lrange(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void lrem(Connection &connection, + const StringView &key, + long long count, + const StringView &val) { + connection.send("LREM %b %lld %b", + key.data(), key.size(), + count, + val.data(), val.size()); +} + +inline void lset(Connection &connection, + const StringView &key, + long long index, + const StringView &val) { + connection.send("LSET %b %lld %b", + key.data(), key.size(), + index, + val.data(), val.size()); +} + +inline void ltrim(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LTRIM %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void rpop(Connection &connection, const StringView &key) { + connection.send("RPOP %b", key.data(), key.size()); +} + +inline void rpoplpush(Connection &connection, + const StringView &source, + const StringView &destination) { + connection.send("RPOPLPUSH %b %b", + source.data(), source.size(), + destination.data(), destination.size()); +} + +inline void rpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void rpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "RPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void rpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +// HASH commands. + +inline void hdel(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HDEL %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +template +inline void hdel_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hexists(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HEXISTS %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hget(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HGET %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hgetall(Connection &connection, const StringView &key) { + connection.send("HGETALL %b", key.data(), key.size()); +} + +inline void hincrby(Connection &connection, + const StringView &key, + const StringView &field, + long long increment) { + connection.send("HINCRBY %b %b %lld", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hincrbyfloat(Connection &connection, + const StringView &key, + const StringView &field, + double increment) { + connection.send("HINCRBYFLOAT %b %b %f", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hkeys(Connection &connection, const StringView &key) { + connection.send("HKEYS %b", key.data(), key.size()); +} + +inline void hlen(Connection &connection, const StringView &key) { + connection.send("HLEN %b", key.data(), key.size()); +} + +template +inline void hmget(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMGET" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void hmset(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMSET" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("HSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void hset(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSET %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +inline void hsetnx(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSETNX %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +inline void hstrlen(Connection &connection, + const StringView &key, + const StringView &field) { + connection.send("HSTRLEN %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hvals(Connection &connection, const StringView &key) { + connection.send("HVALS %b", key.data(), key.size()); +} + +// SET commands + +inline void sadd(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SADD %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void sadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void scard(Connection &connection, const StringView &key) { + connection.send("SCARD %b", key.data(), key.size()); +} + +template +inline void sdiff(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFF" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sdiffstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SDIFFSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sdiffstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFFSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void sinter(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTER" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sinterstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SINTERSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTERSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +inline void sismember(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SISMEMBER %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void smembers(Connection &connection, const StringView &key) { + connection.send("SMEMBERS %b", key.data(), key.size()); +} + +inline void smove(Connection &connection, + const StringView &source, + const StringView &destination, + const StringView &member) { + connection.send("SMOVE %b %b %b", + source.data(), source.size(), + destination.data(), destination.size(), + member.data(), member.size()); +} + +inline void spop(Connection &connection, const StringView &key) { + connection.send("SPOP %b", key.data(), key.size()); +} + +inline void spop_range(Connection &connection, const StringView &key, long long count) { + connection.send("SPOP %b %lld", + key.data(), key.size(), + count); +} + +inline void srandmember(Connection &connection, const StringView &key) { + connection.send("SRANDMEMBER %b", key.data(), key.size()); +} + +inline void srandmember_range(Connection &connection, + const StringView &key, + long long count) { + connection.send("SRANDMEMBER %b %lld", + key.data(), key.size(), + count); +} + +inline void srem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void srem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void sscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +template +inline void sunion(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNION" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sunionstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SUNIONSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNIONSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// Sorted Set commands. + +inline void bzpopmax(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMAX %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmax_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMAX" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void bzpopmin(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMIN %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmin_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMIN" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed); + +inline void zadd(Connection &connection, + const StringView &key, + const StringView &member, + double score, + UpdateType type, + bool changed) { + auto tmp = {std::make_pair(member, score)}; + + zadd_range(connection, key, tmp.begin(), tmp.end(), type, changed); +} + +inline void zcard(Connection &connection, const StringView &key) { + connection.send("ZCARD %b", key.data(), key.size()); +} + +template +inline void zcount(Connection &connection, + const StringView &key, + const Interval &interval) { + connection.send("ZCOUNT %b %s %s", + key.data(), key.size(), + interval.min().c_str(), + interval.max().c_str()); +} + +inline void zincrby(Connection &connection, + const StringView &key, + double increment, + const StringView &member) { + connection.send("ZINCRBY %b %f %b", + key.data(), key.size(), + increment, + member.data(), member.size()); +} + +inline void zinterstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZINTERSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +template +inline void zlexcount(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZLEXCOUNT %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zpopmax(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMAX %b %lld", + key.data(), key.size(), + count); +} + +inline void zpopmin(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMIN %b %lld", + key.data(), key.size(), + count); +} + +inline void zrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); +} + +template +void zrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } else { + connection.send("ZRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } +} + +inline void zrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zrem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void zrem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "ZREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void zremrangebylex(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYLEX %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zremrangebyrank(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("zremrangebyrank %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +template +inline void zremrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYSCORE %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zrevrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZREVRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZREVRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrevrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREVRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); +} + +template +void zrevrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZREVRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } else { + connection.send("ZREVRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } +} + +inline void zrevrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREVRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("ZSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void zscore(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZSCORE %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zunionstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZUNIONSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +// HYPERLOGLOG commands. + +inline void pfadd(Connection &connection, + const StringView &key, + const StringView &element) { + connection.send("PFADD %b %b", + key.data(), key.size(), + element.data(), element.size()); +} + +template +inline void pfadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfcount(Connection &connection, const StringView &key) { + connection.send("PFCOUNT %b", key.data(), key.size()); +} + +template +inline void pfcount_range(Connection &connection, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFCOUNT" << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfmerge(Connection &connection, const StringView &destination, const StringView &key) { + connection.send("PFMERGE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void pfmerge_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFMERGE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// GEO commands. + +inline void geoadd(Connection &connection, + const StringView &key, + const std::tuple &member) { + const auto &mem = std::get<0>(member); + + connection.send("GEOADD %b %f %f %b", + key.data(), key.size(), + std::get<1>(member), + std::get<2>(member), + mem.data(), mem.size()); +} + +template +inline void geoadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOADD" << key; + + while (first != last) { + const auto &member = *first; + args << std::get<1>(member) << std::get<2>(member) << std::get<0>(member); + ++first; + } + + connection.send(args); +} + +void geodist(Connection &connection, + const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit); + +template +inline void geohash_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOHASH" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void geopos_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOPOS" << key << std::make_pair(first, last); + + connection.send(args); +} + +void georadius(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadius_store(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void georadiusbymember(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadiusbymember_store(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +// SCRIPTING commands. + +inline void eval(Connection &connection, + const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + CmdArgs cmd_args; + + cmd_args << "EVAL" << script << keys.size() + << std::make_pair(keys.begin(), keys.end()) + << std::make_pair(args.begin(), args.end()); + + connection.send(cmd_args); +} + +inline void evalsha(Connection &connection, + const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + CmdArgs cmd_args; + + cmd_args << "EVALSHA" << script << keys.size() + << std::make_pair(keys.begin(), keys.end()) + << std::make_pair(args.begin(), args.end()); + + connection.send(cmd_args); +} + +inline void script_exists(Connection &connection, const StringView &sha) { + connection.send("SCRIPT EXISTS %b", sha.data(), sha.size()); +} + +template +inline void script_exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SCRIPT" << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void script_flush(Connection &connection) { + connection.send("SCRIPT FLUSH"); +} + +inline void script_kill(Connection &connection) { + connection.send("SCRIPT KILL"); +} + +inline void script_load(Connection &connection, const StringView &script) { + connection.send("SCRIPT LOAD %b", script.data(), script.size()); +} + +// PUBSUB commands. + +inline void psubscribe(Connection &connection, const StringView &pattern) { + connection.send("PSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void psubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void publish(Connection &connection, + const StringView &channel, + const StringView &message) { + connection.send("PUBLISH %b %b", + channel.data(), channel.size(), + message.data(), message.size()); +} + +inline void punsubscribe(Connection &connection) { + connection.send("PUNSUBSCRIBE"); +} + +inline void punsubscribe(Connection &connection, const StringView &pattern) { + connection.send("PUNSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void punsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PUNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PUNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void subscribe(Connection &connection, const StringView &channel) { + connection.send("SUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void subscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("SUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "SUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void unsubscribe(Connection &connection) { + connection.send("UNSUBSCRIBE"); +} + +inline void unsubscribe(Connection &connection, const StringView &channel) { + connection.send("UNSUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void unsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "UNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +// Transaction commands. + +inline void discard(Connection &connection) { + connection.send("DISCARD"); +} + +inline void exec(Connection &connection) { + connection.send("EXEC"); +} + +inline void multi(Connection &connection) { + connection.send("MULTI"); +} + +inline void unwatch(Connection &connection, const StringView &key) { + connection.send("UNWATCH %b", key.data(), key.size()); +} + +template +inline void unwatch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNWATCH: no key specified"); + } + + CmdArgs args; + args << "UNWATCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void watch(Connection &connection, const StringView &key) { + connection.send("WATCH %b", key.data(), key.size()); +} + +template +inline void watch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("WATCH: no key specified"); + } + + CmdArgs args; + args << "WATCH" << std::make_pair(first, last); + + connection.send(args); +} + +// Stream commands. + +inline void xack(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XACK %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +template +void xack_range(Connection &connection, + const StringView &key, + const StringView &group, + Input first, + Input last) { + CmdArgs args; + args << "XACK" << key << group << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last) { + CmdArgs args; + args << "XADD" << key << id << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_maxlen_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + CmdArgs args; + args << "XADD" << key << "MAXLEN"; + + if (approx) { + args << "~"; + } + + args << count << id << std::make_pair(first, last); + + connection.send(args); +} + +inline void xclaim(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + const StringView &id) { + connection.send("XCLAIM %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size(), + min_idle_time, + id.data(), id.size()); +} + +template +void xclaim_range(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + Input first, + Input last) { + CmdArgs args; + args << "XCLAIM" << key << group << consumer << min_idle_time << std::make_pair(first, last); + + connection.send(args); +} + +inline void xdel(Connection &connection, const StringView &key, const StringView &id) { + connection.send("XDEL %b %b", key.data(), key.size(), id.data(), id.size()); +} + +template +void xdel_range(Connection &connection, const StringView &key, Input first, Input last) { + CmdArgs args; + args << "XDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void xgroup_create(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream) { + CmdArgs args; + args << "XGROUP" << "CREATE" << key << group << id; + + if (mkstream) { + args << "MKSTREAM"; + } + + connection.send(args); +} + +inline void xgroup_setid(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XGROUP SETID %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +inline void xgroup_destroy(Connection &connection, + const StringView &key, + const StringView &group) { + connection.send("XGROUP DESTROY %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xgroup_delconsumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer) { + connection.send("XGROUP DELCONSUMER %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size()); +} + +inline void xlen(Connection &connection, const StringView &key) { + connection.send("XLEN %b", key.data(), key.size()); +} + +inline void xpending(Connection &connection, const StringView &key, const StringView &group) { + connection.send("XPENDING %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xpending_detail(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XPENDING %b %b %b %b %lld", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xpending_per_consumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + connection.send("XPENDING %b %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count, + consumer.data(), consumer.size()); +} + +inline void xrange(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end) { + connection.send("XRANGE %b %b %b", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size()); +} + +inline void xrange_count(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xread(Connection &connection, + const StringView &key, + const StringView &id, + long long count) { + connection.send("XREAD COUNT %lld STREAMS %b %b", + count, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_range(Connection &connection, Input first, Input last, long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xread_block(Connection &connection, + const StringView &key, + const StringView &id, + long long timeout, + long long count) { + connection.send("XREAD COUNT %lld BLOCK %lld STREAMS %b %b", + count, + timeout, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_block_range(Connection &connection, + Input first, + Input last, + long long timeout, + long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "BLOCK" << timeout << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup_block(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_block_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xrevrange(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start) { + connection.send("XREVRANGE %b %b %b", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size()); +} + +inline void xrevrange_count(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + connection.send("XREVRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size(), + count); +} + +void xtrim(Connection &connection, const StringView &key, long long count, bool approx); + +namespace detail { + +void set_bitop(CmdArgs &args, BitOp op); + +void set_update_type(CmdArgs &args, UpdateType type); + +void set_aggregation_type(CmdArgs &args, Aggregation type); + +template +void zinterstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zinterstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +void set_geo_unit(CmdArgs &args, GeoUnit unit); + +void set_georadius_store_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void set_georadius_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +} + +} + +} + +} + +namespace sw { + +namespace redis { + +namespace cmd { + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + + detail::set_bitop(args, op); + + args << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + assert(first != last); + + CmdArgs args; + + args << "ZADD" << key; + + detail::set_update_type(args, type); + + if (changed) { + args << "CH"; + } + + while (first != last) { + // Swap the pair to pair. + args << first->second << first->first; + ++first; + } + + connection.send(args); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zinterstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zunionstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_args.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_args.h new file mode 100644 index 000000000..0beb71e5c --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_args.h @@ -0,0 +1,180 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H + +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +class CmdArgs { +public: + template + CmdArgs& append(Arg &&arg); + + template + CmdArgs& append(Arg &&arg, Args &&...args); + + // All overloads of operator<< are for internal use only. + CmdArgs& operator<<(const StringView &arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& operator<<(T &&arg); + + template + CmdArgs& operator<<(const std::pair &range); + + template + auto operator<<(const std::tuple &) -> + typename std::enable_if::type { + return *this; + } + + template + auto operator<<(const std::tuple &arg) -> + typename std::enable_if::type; + + const char** argv() { + return _argv.data(); + } + + const std::size_t* argv_len() { + return _argv_len.data(); + } + + std::size_t size() const { + return _argv.size(); + } + +private: + // Deep copy. + CmdArgs& _append(std::string arg); + + // Shallow copy. + CmdArgs& _append(const StringView &arg); + + // Shallow copy. + CmdArgs& _append(const char *arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& _append(T &&arg) { + return operator<<(std::forward(arg)); + } + + template + CmdArgs& _append(std::true_type, const std::pair &range); + + template + CmdArgs& _append(std::false_type, const std::pair &range); + + std::vector _argv; + std::vector _argv_len; + + std::list _args; +}; + +template +inline CmdArgs& CmdArgs::append(Arg &&arg) { + return _append(std::forward(arg)); +} + +template +inline CmdArgs& CmdArgs::append(Arg &&arg, Args &&...args) { + _append(std::forward(arg)); + + return append(std::forward(args)...); +} + +inline CmdArgs& CmdArgs::operator<<(const StringView &arg) { + _argv.push_back(arg.data()); + _argv_len.push_back(arg.size()); + + return *this; +} + +template +inline CmdArgs& CmdArgs::operator<<(const std::pair &range) { + return _append(IsKvPair())>::type>(), range); +} + +template ::type>::value, + int>::type> +inline CmdArgs& CmdArgs::operator<<(T &&arg) { + return _append(std::to_string(std::forward(arg))); +} + +template +auto CmdArgs::operator<<(const std::tuple &arg) -> + typename std::enable_if::type { + operator<<(std::get(arg)); + + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(std::string arg) { + _args.push_back(std::move(arg)); + return operator<<(_args.back()); +} + +inline CmdArgs& CmdArgs::_append(const StringView &arg) { + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(const char *arg) { + return operator<<(arg); +} + +template +CmdArgs& CmdArgs::_append(std::false_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << *first; + ++first; + } + + return *this; +} + +template +CmdArgs& CmdArgs::_append(std::true_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << first->first << first->second; + ++first; + } + + return *this; +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.cpp new file mode 100644 index 000000000..0c9254e86 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.cpp @@ -0,0 +1,201 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "command_options.h" +#include "errors.h" + +namespace { + +const std::string NEGATIVE_INFINITY_NUMERIC = "-inf"; +const std::string POSITIVE_INFINITY_NUMERIC = "+inf"; + +const std::string NEGATIVE_INFINITY_STRING = "-"; +const std::string POSITIVE_INFINITY_STRING = "+"; + +std::string unbound(const std::string &bnd); + +std::string bound(const std::string &bnd); + +} + +namespace sw { + +namespace redis { + +const std::string& UnboundedInterval::min() const { + return NEGATIVE_INFINITY_NUMERIC; +} + +const std::string& UnboundedInterval::max() const { + return POSITIVE_INFINITY_NUMERIC; +} + +BoundedInterval::BoundedInterval(double min, double max, BoundType type) : + _min(std::to_string(min)), + _max(std::to_string(max)) { + switch (type) { + case BoundType::CLOSED: + // Do nothing + break; + + case BoundType::OPEN: + _min = unbound(_min); + _max = unbound(_max); + break; + + case BoundType::LEFT_OPEN: + _min = unbound(_min); + break; + + case BoundType::RIGHT_OPEN: + _max = unbound(_max); + break; + + default: + throw Error("Unknow BoundType"); + } +} + +LeftBoundedInterval::LeftBoundedInterval(double min, BoundType type) : + _min(std::to_string(min)) { + switch (type) { + case BoundType::OPEN: + _min = unbound(_min); + break; + + case BoundType::RIGHT_OPEN: + // Do nothing. + break; + + default: + throw Error("Bound type can only be OPEN or RIGHT_OPEN"); + } +} + +const std::string& LeftBoundedInterval::max() const { + return POSITIVE_INFINITY_NUMERIC; +} + +RightBoundedInterval::RightBoundedInterval(double max, BoundType type) : + _max(std::to_string(max)) { + switch (type) { + case BoundType::OPEN: + _max = unbound(_max); + break; + + case BoundType::LEFT_OPEN: + // Do nothing. + break; + + default: + throw Error("Bound type can only be OPEN or LEFT_OPEN"); + } +} + +const std::string& RightBoundedInterval::min() const { + return NEGATIVE_INFINITY_NUMERIC; +} + +const std::string& UnboundedInterval::min() const { + return NEGATIVE_INFINITY_STRING; +} + +const std::string& UnboundedInterval::max() const { + return POSITIVE_INFINITY_STRING; +} + +BoundedInterval::BoundedInterval(const std::string &min, + const std::string &max, + BoundType type) { + switch (type) { + case BoundType::CLOSED: + _min = bound(min); + _max = bound(max); + break; + + case BoundType::OPEN: + _min = unbound(min); + _max = unbound(max); + break; + + case BoundType::LEFT_OPEN: + _min = unbound(min); + _max = bound(max); + break; + + case BoundType::RIGHT_OPEN: + _min = bound(min); + _max = unbound(max); + break; + + default: + throw Error("Unknow BoundType"); + } +} + +LeftBoundedInterval::LeftBoundedInterval(const std::string &min, BoundType type) { + switch (type) { + case BoundType::OPEN: + _min = unbound(min); + break; + + case BoundType::RIGHT_OPEN: + _min = bound(min); + break; + + default: + throw Error("Bound type can only be OPEN or RIGHT_OPEN"); + } +} + +const std::string& LeftBoundedInterval::max() const { + return POSITIVE_INFINITY_STRING; +} + +RightBoundedInterval::RightBoundedInterval(const std::string &max, BoundType type) { + switch (type) { + case BoundType::OPEN: + _max = unbound(max); + break; + + case BoundType::LEFT_OPEN: + _max = bound(max); + break; + + default: + throw Error("Bound type can only be OPEN or LEFT_OPEN"); + } +} + +const std::string& RightBoundedInterval::min() const { + return NEGATIVE_INFINITY_STRING; +} + +} + +} + +namespace { + +std::string unbound(const std::string &bnd) { + return "(" + bnd; +} + +std::string bound(const std::string &bnd) { + return "[" + bnd; +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.h new file mode 100644 index 000000000..ca766c086 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.h @@ -0,0 +1,211 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H + +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +enum class UpdateType { + EXIST, + NOT_EXIST, + ALWAYS +}; + +enum class InsertPosition { + BEFORE, + AFTER +}; + +enum class BoundType { + CLOSED, + OPEN, + LEFT_OPEN, + RIGHT_OPEN +}; + +// (-inf, +inf) +template +class UnboundedInterval; + +// [min, max], (min, max), (min, max], [min, max) +template +class BoundedInterval; + +// [min, +inf), (min, +inf) +template +class LeftBoundedInterval; + +// (-inf, max], (-inf, max) +template +class RightBoundedInterval; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(double min, double max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(double min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(double max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(const std::string &min, const std::string &max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(const std::string &min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(const std::string &max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +struct LimitOptions { + long long offset = 0; + long long count = -1; +}; + +enum class Aggregation { + SUM, + MIN, + MAX +}; + +enum class BitOp { + AND, + OR, + XOR, + NOT +}; + +enum class GeoUnit { + M, + KM, + MI, + FT +}; + +template +struct WithCoord : TupleWithType, T> {}; + +template +struct WithDist : TupleWithType {}; + +template +struct WithHash : TupleWithType {}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.cpp new file mode 100644 index 000000000..87c204084 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.cpp @@ -0,0 +1,305 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "connection.h" +#include +#include "reply.h" +#include "command.h" +#include "command_args.h" + +namespace sw { + +namespace redis { + +ConnectionOptions::ConnectionOptions(const std::string &uri) : + ConnectionOptions(_parse_options(uri)) {} + +ConnectionOptions ConnectionOptions::_parse_options(const std::string &uri) const { + std::string type; + std::string path; + std::tie(type, path) = _split_string(uri, "://"); + + if (path.empty()) { + throw Error("Invalid URI: no path"); + } + + if (type == "tcp") { + return _parse_tcp_options(path); + } else if (type == "unix") { + return _parse_unix_options(path); + } else { + throw Error("Invalid URI: invalid type"); + } +} + +ConnectionOptions ConnectionOptions::_parse_tcp_options(const std::string &path) const { + ConnectionOptions options; + + options.type = ConnectionType::TCP; + + std::string host; + std::string port; + std::tie(host, port) = _split_string(path, ":"); + + options.host = host; + try { + if (!port.empty()) { + options.port = std::stoi(port); + } // else use default port, i.e. 6379. + } catch (const std::exception &) { + throw Error("Invalid URL: invalid port"); + } + + return options; +} + +ConnectionOptions ConnectionOptions::_parse_unix_options(const std::string &path) const { + ConnectionOptions options; + + options.type = ConnectionType::UNIX; + options.path = path; + + return options; +} + +auto ConnectionOptions::_split_string(const std::string &str, const std::string &delimiter) const -> + std::pair { + auto pos = str.rfind(delimiter); + if (pos == std::string::npos) { + return {str, ""}; + } + + return {str.substr(0, pos), str.substr(pos + delimiter.size())}; +} + +class Connection::Connector { +public: + explicit Connector(const ConnectionOptions &opts); + + ContextUPtr connect() const; + +private: + ContextUPtr _connect() const; + + redisContext* _connect_tcp() const; + + redisContext* _connect_unix() const; + + void _set_socket_timeout(redisContext &ctx) const; + + void _enable_keep_alive(redisContext &ctx) const; + + timeval _to_timeval(const std::chrono::milliseconds &dur) const; + + const ConnectionOptions &_opts; +}; + +Connection::Connector::Connector(const ConnectionOptions &opts) : _opts(opts) {} + +Connection::ContextUPtr Connection::Connector::connect() const { + auto ctx = _connect(); + + assert(ctx); + + if (ctx->err != REDIS_OK) { + throw_error(*ctx, "Failed to connect to Redis"); + } + + _set_socket_timeout(*ctx); + + _enable_keep_alive(*ctx); + + return ctx; +} + +Connection::ContextUPtr Connection::Connector::_connect() const { + redisContext *context = nullptr; + switch (_opts.type) { + case ConnectionType::TCP: + context = _connect_tcp(); + break; + + case ConnectionType::UNIX: + context = _connect_unix(); + break; + + default: + // Never goes here. + throw Error("Unkonw connection type"); + } + + if (context == nullptr) { + throw Error("Failed to allocate memory for connection."); + } + + return ContextUPtr(context); +} + +redisContext* Connection::Connector::_connect_tcp() const { + if (_opts.connect_timeout > std::chrono::milliseconds(0)) { + return redisConnectWithTimeout(_opts.host.c_str(), + _opts.port, + _to_timeval(_opts.connect_timeout)); + } else { + return redisConnect(_opts.host.c_str(), _opts.port); + } +} + +redisContext* Connection::Connector::_connect_unix() const { + if (_opts.connect_timeout > std::chrono::milliseconds(0)) { + return redisConnectUnixWithTimeout( + _opts.path.c_str(), + _to_timeval(_opts.connect_timeout)); + } else { + return redisConnectUnix(_opts.path.c_str()); + } +} + +void Connection::Connector::_set_socket_timeout(redisContext &ctx) const { + if (_opts.socket_timeout <= std::chrono::milliseconds(0)) { + return; + } + + if (redisSetTimeout(&ctx, _to_timeval(_opts.socket_timeout)) != REDIS_OK) { + throw_error(ctx, "Failed to set socket timeout"); + } +} + +void Connection::Connector::_enable_keep_alive(redisContext &ctx) const { + if (!_opts.keep_alive) { + return; + } + + if (redisEnableKeepAlive(&ctx) != REDIS_OK) { + throw_error(ctx, "Failed to enable keep alive option"); + } +} + +timeval Connection::Connector::_to_timeval(const std::chrono::milliseconds &dur) const { + auto sec = std::chrono::duration_cast(dur); + auto msec = std::chrono::duration_cast(dur - sec); + + return { + static_cast(sec.count()), + static_cast(msec.count()) + }; +} + +void swap(Connection &lhs, Connection &rhs) noexcept { + std::swap(lhs._ctx, rhs._ctx); + std::swap(lhs._last_active, rhs._last_active); + std::swap(lhs._opts, rhs._opts); +} + +Connection::Connection(const ConnectionOptions &opts) : + _ctx(Connector(opts).connect()), + _last_active(std::chrono::steady_clock::now()), + _opts(opts) { + assert(_ctx && !broken()); + + _set_options(); +} + +void Connection::reconnect() { + Connection connection(_opts); + + swap(*this, connection); +} + +void Connection::send(int argc, const char **argv, const std::size_t *argv_len) { + auto ctx = _context(); + + assert(ctx != nullptr); + + if (redisAppendCommandArgv(ctx, + argc, + argv, + argv_len) != REDIS_OK) { + throw_error(*ctx, "Failed to send command"); + } + + assert(!broken()); +} + +void Connection::send(CmdArgs &args) { + auto ctx = _context(); + + assert(ctx != nullptr); + + if (redisAppendCommandArgv(ctx, + args.size(), + args.argv(), + args.argv_len()) != REDIS_OK) { + throw_error(*ctx, "Failed to send command"); + } + + assert(!broken()); +} + +ReplyUPtr Connection::recv() { + auto *ctx = _context(); + + assert(ctx != nullptr); + + void *r = nullptr; + if (redisGetReply(ctx, &r) != REDIS_OK) { + throw_error(*ctx, "Failed to get reply"); + } + + assert(!broken() && r != nullptr); + + auto reply = ReplyUPtr(static_cast(r)); + + if (reply::is_error(*reply)) { + throw_error(*reply); + } + + return reply; +} + +void Connection::_set_options() { + _auth(); + + _select_db(); +} + +void Connection::_auth() { + if (_opts.password.empty()) { + return; + } + + cmd::auth(*this, _opts.password); + + auto reply = recv(); + + reply::parse(*reply); +} + +void Connection::_select_db() { + if (_opts.db == 0) { + return; + } + + cmd::select(*this, _opts.db); + + auto reply = recv(); + + reply::parse(*reply); +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.h new file mode 100644 index 000000000..5ad419225 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.h @@ -0,0 +1,194 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_H + +#include +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "reply.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +enum class ConnectionType { + TCP = 0, + UNIX +}; + +struct ConnectionOptions { +public: + ConnectionOptions() = default; + + explicit ConnectionOptions(const std::string &uri); + + ConnectionOptions(const ConnectionOptions &) = default; + ConnectionOptions& operator=(const ConnectionOptions &) = default; + + ConnectionOptions(ConnectionOptions &&) = default; + ConnectionOptions& operator=(ConnectionOptions &&) = default; + + ~ConnectionOptions() = default; + + ConnectionType type = ConnectionType::TCP; + + std::string host; + + int port = 6379; + + std::string path; + + std::string password; + + int db = 0; + + bool keep_alive = false; + + std::chrono::milliseconds connect_timeout{0}; + + std::chrono::milliseconds socket_timeout{0}; + +private: + ConnectionOptions _parse_options(const std::string &uri) const; + + ConnectionOptions _parse_tcp_options(const std::string &path) const; + + ConnectionOptions _parse_unix_options(const std::string &path) const; + + auto _split_string(const std::string &str, const std::string &delimiter) const -> + std::pair; +}; + +class CmdArgs; + +class Connection { +public: + explicit Connection(const ConnectionOptions &opts); + + Connection(const Connection &) = delete; + Connection& operator=(const Connection &) = delete; + + Connection(Connection &&) = default; + Connection& operator=(Connection &&) = default; + + ~Connection() = default; + + // Check if the connection is broken. Client needs to do this check + // before sending some command to the connection. If it's broken, + // client needs to reconnect it. + bool broken() const noexcept { + return _ctx->err != REDIS_OK; + } + + void reset() noexcept { + _ctx->err = 0; + } + + void reconnect(); + + auto last_active() const + -> std::chrono::time_point { + return _last_active; + } + + template + void send(const char *format, Args &&...args); + + void send(int argc, const char **argv, const std::size_t *argv_len); + + void send(CmdArgs &args); + + ReplyUPtr recv(); + + const ConnectionOptions& options() const { + return _opts; + } + + friend void swap(Connection &lhs, Connection &rhs) noexcept; + +private: + class Connector; + + struct ContextDeleter { + void operator()(redisContext *context) const { + if (context != nullptr) { + redisFree(context); + } + }; + }; + + using ContextUPtr = std::unique_ptr; + + void _set_options(); + + void _auth(); + + void _select_db(); + + redisContext* _context(); + + ContextUPtr _ctx; + + // The time that the connection is created or the time that + // the connection is used, i.e. *context()* is called. + std::chrono::time_point _last_active{}; + + ConnectionOptions _opts; +}; + +using ConnectionSPtr = std::shared_ptr; + +enum class Role { + MASTER, + SLAVE +}; + +// Inline implementaions. + +template +inline void Connection::send(const char *format, Args &&...args) { + auto ctx = _context(); + + assert(ctx != nullptr); + + if (redisAppendCommand(ctx, + format, + std::forward(args)...) != REDIS_OK) { + throw_error(*ctx, "Failed to send command"); + } + + assert(!broken()); +} + +inline redisContext* Connection::_context() { + _last_active = std::chrono::steady_clock::now(); + + return _ctx.get(); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.cpp new file mode 100644 index 000000000..f00edb027 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.cpp @@ -0,0 +1,249 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "connection_pool.h" +#include +#include "errors.h" + +namespace sw { + +namespace redis { + +ConnectionPool::ConnectionPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts) : + _opts(connection_opts), + _pool_opts(pool_opts) { + if (_pool_opts.size == 0) { + throw Error("CANNOT create an empty pool"); + } + + // Lazily create connections. +} + +ConnectionPool::ConnectionPool(SimpleSentinel sentinel, + const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts) : + _opts(connection_opts), + _pool_opts(pool_opts), + _sentinel(std::move(sentinel)) { + // In this case, the connection must be of TCP type. + if (_opts.type != ConnectionType::TCP) { + throw Error("Sentinel only supports TCP connection"); + } + + if (_opts.connect_timeout == std::chrono::milliseconds(0) + || _opts.socket_timeout == std::chrono::milliseconds(0)) { + throw Error("With sentinel, connection timeout and socket timeout cannot be 0"); + } + + // Cleanup connection options. + _update_connection_opts("", -1); + + assert(_sentinel); +} + +ConnectionPool::ConnectionPool(ConnectionPool &&that) { + std::lock_guard lock(that._mutex); + + _move(std::move(that)); +} + +ConnectionPool& ConnectionPool::operator=(ConnectionPool &&that) { + if (this != &that) { + std::lock(_mutex, that._mutex); + std::lock_guard lock_this(_mutex, std::adopt_lock); + std::lock_guard lock_that(that._mutex, std::adopt_lock); + + _move(std::move(that)); + } + + return *this; +} + +Connection ConnectionPool::fetch() { + std::unique_lock lock(_mutex); + + if (_pool.empty()) { + if (_used_connections == _pool_opts.size) { + _wait_for_connection(lock); + } else { + // Lazily create a new connection. + auto connection = _create(); + + ++_used_connections; + + return connection; + } + } + + // _pool is NOT empty. + auto connection = _fetch(); + + auto connection_lifetime = _pool_opts.connection_lifetime; + + if (_sentinel) { + auto opts = _opts; + auto role_changed = _role_changed(connection.options()); + auto sentinel = _sentinel; + + lock.unlock(); + + if (role_changed || _need_reconnect(connection, connection_lifetime)) { + try { + connection = _create(sentinel, opts, false); + } catch (const Error &e) { + // Failed to reconnect, return it to the pool, and retry latter. + release(std::move(connection)); + throw; + } + } + + return connection; + } + + lock.unlock(); + + if (_need_reconnect(connection, connection_lifetime)) { + try { + connection.reconnect(); + } catch (const Error &e) { + // Failed to reconnect, return it to the pool, and retry latter. + release(std::move(connection)); + throw; + } + } + + return connection; +} + +ConnectionOptions ConnectionPool::connection_options() { + std::lock_guard lock(_mutex); + + return _opts; +} + +void ConnectionPool::release(Connection connection) { + { + std::lock_guard lock(_mutex); + + _pool.push_back(std::move(connection)); + } + + _cv.notify_one(); +} + +Connection ConnectionPool::create() { + std::unique_lock lock(_mutex); + + auto opts = _opts; + + if (_sentinel) { + auto sentinel = _sentinel; + + lock.unlock(); + + return _create(sentinel, opts, false); + } else { + lock.unlock(); + + return Connection(opts); + } +} + +void ConnectionPool::_move(ConnectionPool &&that) { + _opts = std::move(that._opts); + _pool_opts = std::move(that._pool_opts); + _pool = std::move(that._pool); + _used_connections = that._used_connections; + _sentinel = std::move(that._sentinel); +} + +Connection ConnectionPool::_create() { + if (_sentinel) { + // Get Redis host and port info from sentinel. + return _create(_sentinel, _opts, true); + } + + return Connection(_opts); +} + +Connection ConnectionPool::_create(SimpleSentinel &sentinel, + const ConnectionOptions &opts, + bool locked) { + try { + auto connection = sentinel.create(opts); + + std::unique_lock lock(_mutex, std::defer_lock); + if (!locked) { + lock.lock(); + } + + const auto &connection_opts = connection.options(); + if (_role_changed(connection_opts)) { + // Master/Slave has been changed, reconnect all connections. + _update_connection_opts(connection_opts.host, connection_opts.port); + } + + return connection; + } catch (const StopIterError &e) { + throw Error("Failed to create connection with sentinel"); + } +} + +Connection ConnectionPool::_fetch() { + assert(!_pool.empty()); + + auto connection = std::move(_pool.front()); + _pool.pop_front(); + + return connection; +} + +void ConnectionPool::_wait_for_connection(std::unique_lock &lock) { + auto timeout = _pool_opts.wait_timeout; + if (timeout > std::chrono::milliseconds(0)) { + // Wait until _pool is no longer empty or timeout. + if (!_cv.wait_for(lock, + timeout, + [this] { return !(this->_pool).empty(); })) { + throw Error("Failed to fetch a connection in " + + std::to_string(timeout.count()) + " milliseconds"); + } + } else { + // Wait forever. + _cv.wait(lock, [this] { return !(this->_pool).empty(); }); + } +} + +bool ConnectionPool::_need_reconnect(const Connection &connection, + const std::chrono::milliseconds &connection_lifetime) const { + if (connection.broken()) { + return true; + } + + if (connection_lifetime > std::chrono::milliseconds(0)) { + auto now = std::chrono::steady_clock::now(); + if (now - connection.last_active() > connection_lifetime) { + return true; + } + } + + return false; +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.h new file mode 100644 index 000000000..6f2663ad7 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.h @@ -0,0 +1,115 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +struct ConnectionPoolOptions { + // Max number of connections, including both in-use and idle ones. + std::size_t size = 1; + + // Max time to wait for a connection. 0ms means client waits forever. + std::chrono::milliseconds wait_timeout{0}; + + // Max lifetime of a connection. 0ms means we never expire the connection. + std::chrono::milliseconds connection_lifetime{0}; +}; + +class ConnectionPool { +public: + ConnectionPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool(SimpleSentinel sentinel, + const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool() = default; + + ConnectionPool(ConnectionPool &&that); + ConnectionPool& operator=(ConnectionPool &&that); + + ConnectionPool(const ConnectionPool &) = delete; + ConnectionPool& operator=(const ConnectionPool &) = delete; + + ~ConnectionPool() = default; + + // Fetch a connection from pool. + Connection fetch(); + + ConnectionOptions connection_options(); + + void release(Connection connection); + + // Create a new connection. + Connection create(); + +private: + void _move(ConnectionPool &&that); + + // NOT thread-safe + Connection _create(); + + Connection _create(SimpleSentinel &sentinel, const ConnectionOptions &opts, bool locked); + + Connection _fetch(); + + void _wait_for_connection(std::unique_lock &lock); + + bool _need_reconnect(const Connection &connection, + const std::chrono::milliseconds &connection_lifetime) const; + + void _update_connection_opts(const std::string &host, int port) { + _opts.host = host; + _opts.port = port; + } + + bool _role_changed(const ConnectionOptions &opts) const { + return opts.port != _opts.port || opts.host != _opts.host; + } + + ConnectionOptions _opts; + + ConnectionPoolOptions _pool_opts; + + std::deque _pool; + + std::size_t _used_connections = 0; + + std::mutex _mutex; + + std::condition_variable _cv; + + SimpleSentinel _sentinel; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/crc16.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/crc16.cpp new file mode 100644 index 000000000..c94a08d30 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/crc16.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2001-2010 Georges Menie (www.menie.org) + * Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style) + * 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 the University of California, Berkeley 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 REGENTS 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 REGENTS AND 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. + */ + +/* CRC16 implementation according to CCITT standards. + * + * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the + * following parameters: + * + * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" + * Width : 16 bit + * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) + * Initialization : 0000 + * Reflect Input byte : False + * Reflect Output CRC : False + * Xor constant to output CRC : 0000 + * Output for "123456789" : 31C3 + */ + +#include + +namespace sw { + +namespace redis { + +static const uint16_t crc16tab[256]= { + 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, + 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, + 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, + 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, + 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, + 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, + 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, + 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, + 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, + 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, + 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, + 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, + 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, + 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, + 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, + 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, + 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, + 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, + 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, + 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, + 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, + 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, + 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, + 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, + 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, + 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, + 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, + 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, + 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, + 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, + 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, + 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 +}; + +uint16_t crc16(const char *buf, int len) { + int counter; + uint16_t crc = 0; + for (counter = 0; counter < len; counter++) + crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF]; + return crc; +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.cpp new file mode 100644 index 000000000..5d5fe7dc9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.cpp @@ -0,0 +1,136 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "errors.h" +#include +#include +#include +#include +#include "shards.h" + +namespace { + +using namespace sw::redis; + +std::pair parse_error(const std::string &msg); + +std::unordered_map error_map = { + {"MOVED", ReplyErrorType::MOVED}, + {"ASK", ReplyErrorType::ASK} +}; + +} + +namespace sw { + +namespace redis { + +void throw_error(redisContext &context, const std::string &err_info) { + auto err_code = context.err; + const auto *err_str = context.errstr; + if (err_str == nullptr) { + throw Error(err_info + ": null error message: " + std::to_string(err_code)); + } + + auto err_msg = err_info + ": " + err_str; + + switch (err_code) { + case REDIS_ERR_IO: + if (errno == EAGAIN || errno == EINTR) { + throw TimeoutError(err_msg); + } else { + throw IoError(err_msg); + } + break; + + case REDIS_ERR_EOF: + throw ClosedError(err_msg); + break; + + case REDIS_ERR_PROTOCOL: + throw ProtoError(err_msg); + break; + + case REDIS_ERR_OOM: + throw OomError(err_msg); + break; + + case REDIS_ERR_OTHER: + throw Error(err_msg); + break; + + default: + throw Error(err_info + ": Unknown error code"); + } +} + +void throw_error(const redisReply &reply) { + assert(reply.type == REDIS_REPLY_ERROR); + + if (reply.str == nullptr) { + throw Error("Null error reply"); + } + + auto err_str = std::string(reply.str, reply.len); + + auto err_type = ReplyErrorType::ERR; + std::string err_msg; + std::tie(err_type, err_msg) = parse_error(err_str); + + switch (err_type) { + case ReplyErrorType::MOVED: + throw MovedError(err_msg); + break; + + case ReplyErrorType::ASK: + throw AskError(err_msg); + break; + + default: + throw ReplyError(err_str); + break; + } +} + +} + +} + +namespace { + +using namespace sw::redis; + +std::pair parse_error(const std::string &err) { + // The error contains an Error Prefix, and an optional error message. + auto idx = err.find_first_of(" \n"); + + if (idx == std::string::npos) { + throw ProtoError("No Error Prefix: " + err); + } + + auto err_prefix = err.substr(0, idx); + auto err_type = ReplyErrorType::ERR; + + auto iter = error_map.find(err_prefix); + if (iter != error_map.end()) { + // Specific error. + err_type = iter->second; + } // else Generic error. + + return {err_type, err.substr(idx + 1)}; +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.h new file mode 100644 index 000000000..44d629e50 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.h @@ -0,0 +1,159 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_ERRORS_H +#define SEWENEW_REDISPLUSPLUS_ERRORS_H + +#include +#include +#include + +namespace sw { + +namespace redis { + +enum ReplyErrorType { + ERR, + MOVED, + ASK +}; + +class Error : public std::exception { +public: + explicit Error(const std::string &msg) : _msg(msg) {} + + Error(const Error &) = default; + Error& operator=(const Error &) = default; + + Error(Error &&) = default; + Error& operator=(Error &&) = default; + + virtual ~Error() = default; + + virtual const char* what() const noexcept { + return _msg.data(); + } + +private: + std::string _msg; +}; + +class IoError : public Error { +public: + explicit IoError(const std::string &msg) : Error(msg) {} + + IoError(const IoError &) = default; + IoError& operator=(const IoError &) = default; + + IoError(IoError &&) = default; + IoError& operator=(IoError &&) = default; + + virtual ~IoError() = default; +}; + +class TimeoutError : public IoError { +public: + explicit TimeoutError(const std::string &msg) : IoError(msg) {} + + TimeoutError(const TimeoutError &) = default; + TimeoutError& operator=(const TimeoutError &) = default; + + TimeoutError(TimeoutError &&) = default; + TimeoutError& operator=(TimeoutError &&) = default; + + virtual ~TimeoutError() = default; +}; + +class ClosedError : public Error { +public: + explicit ClosedError(const std::string &msg) : Error(msg) {} + + ClosedError(const ClosedError &) = default; + ClosedError& operator=(const ClosedError &) = default; + + ClosedError(ClosedError &&) = default; + ClosedError& operator=(ClosedError &&) = default; + + virtual ~ClosedError() = default; +}; + +class ProtoError : public Error { +public: + explicit ProtoError(const std::string &msg) : Error(msg) {} + + ProtoError(const ProtoError &) = default; + ProtoError& operator=(const ProtoError &) = default; + + ProtoError(ProtoError &&) = default; + ProtoError& operator=(ProtoError &&) = default; + + virtual ~ProtoError() = default; +}; + +class OomError : public Error { +public: + explicit OomError(const std::string &msg) : Error(msg) {} + + OomError(const OomError &) = default; + OomError& operator=(const OomError &) = default; + + OomError(OomError &&) = default; + OomError& operator=(OomError &&) = default; + + virtual ~OomError() = default; +}; + +class ReplyError : public Error { +public: + explicit ReplyError(const std::string &msg) : Error(msg) {} + + ReplyError(const ReplyError &) = default; + ReplyError& operator=(const ReplyError &) = default; + + ReplyError(ReplyError &&) = default; + ReplyError& operator=(ReplyError &&) = default; + + virtual ~ReplyError() = default; +}; + +class WatchError : public Error { +public: + explicit WatchError() : Error("Watched key has been modified") {} + + WatchError(const WatchError &) = default; + WatchError& operator=(const WatchError &) = default; + + WatchError(WatchError &&) = default; + WatchError& operator=(WatchError &&) = default; + + virtual ~WatchError() = default; +}; + + +// MovedError and AskError are defined in shards.h +class MovedError; + +class AskError; + +void throw_error(redisContext &context, const std::string &err_info); + +void throw_error(const redisReply &reply); + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_ERRORS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.cpp new file mode 100644 index 000000000..141f96ea2 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.cpp @@ -0,0 +1,35 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "pipeline.h" + +namespace sw { + +namespace redis { + +std::vector PipelineImpl::exec(Connection &connection, std::size_t cmd_num) { + std::vector replies; + while (cmd_num > 0) { + replies.push_back(connection.recv()); + --cmd_num; + } + + return replies; +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.h new file mode 100644 index 000000000..52b01253f --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.h @@ -0,0 +1,49 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_PIPELINE_H +#define SEWENEW_REDISPLUSPLUS_PIPELINE_H + +#include +#include +#include "connection.h" + +namespace sw { + +namespace redis { + +class PipelineImpl { +public: + template + void command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + } + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t /*cmd_num*/) { + // Reconnect to Redis to discard all commands. + connection.reconnect(); + } +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_PIPELINE_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.h new file mode 100644 index 000000000..71d975ee3 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.h @@ -0,0 +1,1844 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H + +#include +#include +#include +#include +#include "connection.h" +#include "utils.h" +#include "reply.h" +#include "command.h" +#include "redis.h" + +namespace sw { + +namespace redis { + +class QueuedReplies; + +// If any command throws, QueuedRedis resets the connection, and becomes invalid. +// In this case, the only thing we can do is to destory the QueuedRedis object. +template +class QueuedRedis { +public: + QueuedRedis(QueuedRedis &&) = default; + QueuedRedis& operator=(QueuedRedis &&) = default; + + // When it destructs, the underlying *Connection* will be closed, + // and any command that has NOT been executed will be ignored. + ~QueuedRedis() = default; + + Redis redis(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type; + + template + QueuedRedis& command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type; + + QueuedReplies exec(); + + void discard(); + + // CONNECTION commands. + + QueuedRedis& auth(const StringView &password) { + return command(cmd::auth, password); + } + + QueuedRedis& echo(const StringView &msg) { + return command(cmd::echo, msg); + } + + QueuedRedis& ping() { + return command(cmd::ping); + } + + QueuedRedis& ping(const StringView &msg) { + return command(cmd::ping, msg); + } + + // We DO NOT support the QUIT command. See *Redis::quit* doc for details. + // + // QueuedRedis& quit(); + + QueuedRedis& select(long long idx) { + return command(cmd::select, idx); + } + + QueuedRedis& swapdb(long long idx1, long long idx2) { + return command(cmd::swapdb, idx1, idx2); + } + + // SERVER commands. + + QueuedRedis& bgrewriteaof() { + return command(cmd::bgrewriteaof); + } + + QueuedRedis& bgsave() { + return command(cmd::bgsave); + } + + QueuedRedis& dbsize() { + return command(cmd::dbsize); + } + + QueuedRedis& flushall(bool async = false) { + return command(cmd::flushall, async); + } + + QueuedRedis& flushdb(bool async = false) { + return command(cmd::flushdb, async); + } + + QueuedRedis& info() { + return command(cmd::info); + } + + QueuedRedis& info(const StringView §ion) { + return command(cmd::info, section); + } + + QueuedRedis& lastsave() { + return command(cmd::lastsave); + } + + QueuedRedis& save() { + return command(cmd::save); + } + + // KEY commands. + + QueuedRedis& del(const StringView &key) { + return command(cmd::del, key); + } + + template + QueuedRedis& del(Input first, Input last) { + return command(cmd::del_range, first, last); + } + + template + QueuedRedis& del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + QueuedRedis& dump(const StringView &key) { + return command(cmd::dump, key); + } + + QueuedRedis& exists(const StringView &key) { + return command(cmd::exists, key); + } + + template + QueuedRedis& exists(Input first, Input last) { + return command(cmd::exists_range, first, last); + } + + template + QueuedRedis& exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + QueuedRedis& expire(const StringView &key, long long timeout) { + return command(cmd::expire, key, timeout); + } + + QueuedRedis& expire(const StringView &key, + const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); + } + + QueuedRedis& expireat(const StringView &key, long long timestamp) { + return command(cmd::expireat, key, timestamp); + } + + QueuedRedis& expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& keys(const StringView &pattern) { + return command(cmd::keys, pattern); + } + + QueuedRedis& move(const StringView &key, long long db) { + return command(cmd::move, key, db); + } + + QueuedRedis& persist(const StringView &key) { + return command(cmd::persist, key); + } + + QueuedRedis& pexpire(const StringView &key, long long timeout) { + return command(cmd::pexpire, key, timeout); + } + + QueuedRedis& pexpire(const StringView &key, + const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); + } + + QueuedRedis& pexpireat(const StringView &key, long long timestamp) { + return command(cmd::pexpireat, key, timestamp); + } + + QueuedRedis& pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& pttl(const StringView &key) { + return command(cmd::pttl, key); + } + + QueuedRedis& randomkey() { + return command(cmd::randomkey); + } + + QueuedRedis& rename(const StringView &key, const StringView &newkey) { + return command(cmd::rename, key, newkey); + } + + QueuedRedis& renamenx(const StringView &key, const StringView &newkey) { + return command(cmd::renamenx, key, newkey); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false) { + return command(cmd::restore, key, val, ttl, replace); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false) { + return restore(key, val, ttl.count(), replace); + } + + // TODO: sort + + QueuedRedis& scan(long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::scan, cursor, pattern, count); + } + + QueuedRedis& scan(long long cursor) { + return scan(cursor, "*", 10); + } + + QueuedRedis& scan(long long cursor, + const StringView &pattern) { + return scan(cursor, pattern, 10); + } + + QueuedRedis& scan(long long cursor, + long long count) { + return scan(cursor, "*", count); + } + + QueuedRedis& touch(const StringView &key) { + return command(cmd::touch, key); + } + + template + QueuedRedis& touch(Input first, Input last) { + return command(cmd::touch_range, first, last); + } + + template + QueuedRedis& touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + QueuedRedis& ttl(const StringView &key) { + return command(cmd::ttl, key); + } + + QueuedRedis& type(const StringView &key) { + return command(cmd::type, key); + } + + QueuedRedis& unlink(const StringView &key) { + return command(cmd::unlink, key); + } + + template + QueuedRedis& unlink(Input first, Input last) { + return command(cmd::unlink_range, first, last); + } + + template + QueuedRedis& unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + QueuedRedis& wait(long long numslaves, long long timeout) { + return command(cmd::wait, numslaves, timeout); + } + + QueuedRedis& wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); + } + + // STRING commands. + + QueuedRedis& append(const StringView &key, const StringView &str) { + return command(cmd::append, key, str); + } + + QueuedRedis& bitcount(const StringView &key, + long long start = 0, + long long end = -1) { + return command(cmd::bitcount, key, start, end); + } + + QueuedRedis& bitop(BitOp op, + const StringView &destination, + const StringView &key) { + return command(cmd::bitop, op, destination, key); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + Input first, + Input last) { + return command(cmd::bitop_range, op, destination, first, last); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + QueuedRedis& bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1) { + return command(cmd::bitpos, key, bit, start, end); + } + + QueuedRedis& decr(const StringView &key) { + return command(cmd::decr, key); + } + + QueuedRedis& decrby(const StringView &key, long long decrement) { + return command(cmd::decrby, key, decrement); + } + + QueuedRedis& get(const StringView &key) { + return command(cmd::get, key); + } + + QueuedRedis& getbit(const StringView &key, long long offset) { + return command(cmd::getbit, key, offset); + } + + QueuedRedis& getrange(const StringView &key, long long start, long long end) { + return command(cmd::getrange, key, start, end); + } + + QueuedRedis& getset(const StringView &key, const StringView &val) { + return command(cmd::getset, key, val); + } + + QueuedRedis& incr(const StringView &key) { + return command(cmd::incr, key); + } + + QueuedRedis& incrby(const StringView &key, long long increment) { + return command(cmd::incrby, key, increment); + } + + QueuedRedis& incrbyfloat(const StringView &key, double increment) { + return command(cmd::incrbyfloat, key, increment); + } + + template + QueuedRedis& mget(Input first, Input last) { + return command(cmd::mget, first, last); + } + + template + QueuedRedis& mget(std::initializer_list il) { + return mget(il.begin(), il.end()); + } + + template + QueuedRedis& mset(Input first, Input last) { + return command(cmd::mset, first, last); + } + + template + QueuedRedis& mset(std::initializer_list il) { + return mset(il.begin(), il.end()); + } + + template + QueuedRedis& msetnx(Input first, Input last) { + return command(cmd::msetnx, first, last); + } + + template + QueuedRedis& msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + QueuedRedis& psetex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::psetex, key, ttl, val); + } + + QueuedRedis& psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); + } + + QueuedRedis& set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS) { + _set_cmd_indexes.push_back(_cmd_num); + + return command(cmd::set, key, val, ttl.count(), type); + } + + QueuedRedis& setex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::setex, key, ttl, val); + } + + QueuedRedis& setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + return setex(key, ttl.count(), val); + } + + QueuedRedis& setnx(const StringView &key, const StringView &val) { + return command(cmd::setnx, key, val); + } + + QueuedRedis& setrange(const StringView &key, + long long offset, + const StringView &val) { + return command(cmd::setrange, key, offset, val); + } + + QueuedRedis& strlen(const StringView &key) { + return command(cmd::strlen, key); + } + + // LIST commands. + + QueuedRedis& blpop(const StringView &key, long long timeout) { + return command(cmd::blpop, key, timeout); + } + + QueuedRedis& blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(key, timeout.count()); + } + + template + QueuedRedis& blpop(Input first, Input last, long long timeout) { + return command(cmd::blpop_range, first, last, timeout); + } + + template + QueuedRedis& blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(first, last, timeout.count()); + } + + template + QueuedRedis& blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpop(const StringView &key, long long timeout) { + return command(cmd::brpop, key, timeout); + } + + QueuedRedis& brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(key, timeout.count()); + } + + template + QueuedRedis& brpop(Input first, Input last, long long timeout) { + return command(cmd::brpop_range, first, last, timeout); + } + + template + QueuedRedis& brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(first, last, timeout.count()); + } + + template + QueuedRedis& brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + long long timeout) { + return command(cmd::brpoplpush, source, destination, timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpoplpush(source, destination, timeout.count()); + } + + QueuedRedis& lindex(const StringView &key, long long index) { + return command(cmd::lindex, key, index); + } + + QueuedRedis& linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val) { + return command(cmd::linsert, key, position, pivot, val); + } + + QueuedRedis& llen(const StringView &key) { + return command(cmd::llen, key); + } + + QueuedRedis& lpop(const StringView &key) { + return command(cmd::lpop, key); + } + + QueuedRedis& lpush(const StringView &key, const StringView &val) { + return command(cmd::lpush, key, val); + } + + template + QueuedRedis& lpush(const StringView &key, Input first, Input last) { + return command(cmd::lpush_range, key, first, last); + } + + template + QueuedRedis& lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + QueuedRedis& lpushx(const StringView &key, const StringView &val) { + return command(cmd::lpushx, key, val); + } + + QueuedRedis& lrange(const StringView &key, + long long start, + long long stop) { + return command(cmd::lrange, key, start, stop); + } + + QueuedRedis& lrem(const StringView &key, long long count, const StringView &val) { + return command(cmd::lrem, key, count, val); + } + + QueuedRedis& lset(const StringView &key, long long index, const StringView &val) { + return command(cmd::lset, key, index, val); + } + + QueuedRedis& ltrim(const StringView &key, long long start, long long stop) { + return command(cmd::ltrim, key, start, stop); + } + + QueuedRedis& rpop(const StringView &key) { + return command(cmd::rpop, key); + } + + QueuedRedis& rpoplpush(const StringView &source, const StringView &destination) { + return command(cmd::rpoplpush, source, destination); + } + + QueuedRedis& rpush(const StringView &key, const StringView &val) { + return command(cmd::rpush, key, val); + } + + template + QueuedRedis& rpush(const StringView &key, Input first, Input last) { + return command(cmd::rpush_range, key, first, last); + } + + template + QueuedRedis& rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + QueuedRedis& rpushx(const StringView &key, const StringView &val) { + return command(cmd::rpushx, key, val); + } + + // HASH commands. + + QueuedRedis& hdel(const StringView &key, const StringView &field) { + return command(cmd::hdel, key, field); + } + + template + QueuedRedis& hdel(const StringView &key, Input first, Input last) { + return command(cmd::hdel_range, key, first, last); + } + + template + QueuedRedis& hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + QueuedRedis& hexists(const StringView &key, const StringView &field) { + return command(cmd::hexists, key, field); + } + + QueuedRedis& hget(const StringView &key, const StringView &field) { + return command(cmd::hget, key, field); + } + + QueuedRedis& hgetall(const StringView &key) { + return command(cmd::hgetall, key); + } + + QueuedRedis& hincrby(const StringView &key, + const StringView &field, + long long increment) { + return command(cmd::hincrby, key, field, increment); + } + + QueuedRedis& hincrbyfloat(const StringView &key, + const StringView &field, + double increment) { + return command(cmd::hincrbyfloat, key, field, increment); + } + + QueuedRedis& hkeys(const StringView &key) { + return command(cmd::hkeys, key); + } + + QueuedRedis& hlen(const StringView &key) { + return command(cmd::hlen, key); + } + + template + QueuedRedis& hmget(const StringView &key, Input first, Input last) { + return command(cmd::hmget, key, first, last); + } + + template + QueuedRedis& hmget(const StringView &key, std::initializer_list il) { + return hmget(key, il.begin(), il.end()); + } + + template + QueuedRedis& hmset(const StringView &key, Input first, Input last) { + return command(cmd::hmset, key, first, last); + } + + template + QueuedRedis& hmset(const StringView &key, std::initializer_list il) { + return hmset(key, il.begin(), il.end()); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::hscan, key, cursor, pattern, count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return hscan(key, cursor, pattern, 10); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + long long count) { + return hscan(key, cursor, "*", count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor) { + return hscan(key, cursor, "*", 10); + } + + QueuedRedis& hset(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hset, key, field, val); + } + + QueuedRedis& hset(const StringView &key, const std::pair &item) { + return hset(key, item.first, item.second); + } + + QueuedRedis& hsetnx(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hsetnx, key, field, val); + } + + QueuedRedis& hsetnx(const StringView &key, const std::pair &item) { + return hsetnx(key, item.first, item.second); + } + + QueuedRedis& hstrlen(const StringView &key, const StringView &field) { + return command(cmd::hstrlen, key, field); + } + + QueuedRedis& hvals(const StringView &key) { + return command(cmd::hvals, key); + } + + // SET commands. + + QueuedRedis& sadd(const StringView &key, const StringView &member) { + return command(cmd::sadd, key, member); + } + + template + QueuedRedis& sadd(const StringView &key, Input first, Input last) { + return command(cmd::sadd_range, key, first, last); + } + + template + QueuedRedis& sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + QueuedRedis& scard(const StringView &key) { + return command(cmd::scard, key); + } + + template + QueuedRedis& sdiff(Input first, Input last) { + return command(cmd::sdiff, first, last); + } + + template + QueuedRedis& sdiff(std::initializer_list il) { + return sdiff(il.begin(), il.end()); + } + + QueuedRedis& sdiffstore(const StringView &destination, const StringView &key) { + return command(cmd::sdiffstore, destination, key); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, + Input first, + Input last) { + return command(cmd::sdiffstore_range, destination, first, last); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + QueuedRedis& sinter(Input first, Input last) { + return command(cmd::sinter, first, last); + } + + template + QueuedRedis& sinter(std::initializer_list il) { + return sinter(il.begin(), il.end()); + } + + QueuedRedis& sinterstore(const StringView &destination, const StringView &key) { + return command(cmd::sinterstore, destination, key); + } + + template + QueuedRedis& sinterstore(const StringView &destination, + Input first, + Input last) { + return command(cmd::sinterstore_range, destination, first, last); + } + + template + QueuedRedis& sinterstore(const StringView &destination, std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + QueuedRedis& sismember(const StringView &key, const StringView &member) { + return command(cmd::sismember, key, member); + } + + QueuedRedis& smembers(const StringView &key) { + return command(cmd::smembers, key); + } + + QueuedRedis& smove(const StringView &source, + const StringView &destination, + const StringView &member) { + return command(cmd::smove, source, destination, member); + } + + QueuedRedis& spop(const StringView &key) { + return command(cmd::spop, key); + } + + QueuedRedis& spop(const StringView &key, long long count) { + return command(cmd::spop_range, key, count); + } + + QueuedRedis& srandmember(const StringView &key) { + return command(cmd::srandmember, key); + } + + QueuedRedis& srandmember(const StringView &key, long long count) { + return command(cmd::srandmember_range, key, count); + } + + QueuedRedis& srem(const StringView &key, const StringView &member) { + return command(cmd::srem, key, member); + } + + template + QueuedRedis& srem(const StringView &key, Input first, Input last) { + return command(cmd::srem_range, key, first, last); + } + + template + QueuedRedis& srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::sscan, key, cursor, pattern, count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return sscan(key, cursor, pattern, 10); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + long long count) { + return sscan(key, cursor, "*", count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor) { + return sscan(key, cursor, "*", 10); + } + + template + QueuedRedis& sunion(Input first, Input last) { + return command(cmd::sunion, first, last); + } + + template + QueuedRedis& sunion(std::initializer_list il) { + return sunion(il.begin(), il.end()); + } + + QueuedRedis& sunionstore(const StringView &destination, const StringView &key) { + return command(cmd::sunionstore, destination, key); + } + + template + QueuedRedis& sunionstore(const StringView &destination, Input first, Input last) { + return command(cmd::sunionstore_range, destination, first, last); + } + + template + QueuedRedis& sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + QueuedRedis& bzpopmax(const StringView &key, long long timeout) { + return command(cmd::bzpopmax, key, timeout); + } + + QueuedRedis& bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(key, timeout.count()); + } + + template + QueuedRedis& bzpopmax(Input first, Input last, long long timeout) { + return command(cmd::bzpopmax_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, long long timeout) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, long long timeout) { + return command(cmd::bzpopmin, key, timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(key, timeout.count()); + } + + template + QueuedRedis& bzpopmin(Input first, Input last, long long timeout) { + return command(cmd::bzpopmin_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, long long timeout) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + QueuedRedis& zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return command(cmd::zadd, key, member, score, type, changed); + } + + template + QueuedRedis& zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return command(cmd::zadd_range, key, first, last, type, changed); + } + + QueuedRedis& zcard(const StringView &key) { + return command(cmd::zcard, key); + } + + template + QueuedRedis& zcount(const StringView &key, const Interval &interval) { + return command(cmd::zcount, key, interval); + } + + QueuedRedis& zincrby(const StringView &key, double increment, const StringView &member) { + return command(cmd::zincrby, key, increment, member); + } + + QueuedRedis& zinterstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zinterstore, destination, key, weight); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + return command(cmd::zinterstore_range, destination, first, last, type); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + QueuedRedis& zlexcount(const StringView &key, const Interval &interval) { + return command(cmd::zlexcount, key, interval); + } + + QueuedRedis& zpopmax(const StringView &key) { + return command(cmd::zpopmax, key, 1); + } + + QueuedRedis& zpopmax(const StringView &key, long long count) { + return command(cmd::zpopmax, key, count); + } + + QueuedRedis& zpopmin(const StringView &key) { + return command(cmd::zpopmin, key, 1); + } + + QueuedRedis& zpopmin(const StringView &key, long long count) { + return command(cmd::zpopmin, key, count); + } + + // NOTE: *QueuedRedis::zrange*'s parameters are different from *Redis::zrange*. + // *Redis::zrange* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::zrange*. So we have to use an extra parameter: *with_scores*, + // to decide whether we should send *WITHSCORES* option to Redis. This also applies to + // other commands with the *WITHSCORES* option, e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, + // *ZREVRANGEBYSCORE*. + QueuedRedis& zrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrangebylex(const StringView &key, const Interval &interval) { + return zrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrank(const StringView &key, const StringView &member) { + return command(cmd::zrank, key, member); + } + + QueuedRedis& zrem(const StringView &key, const StringView &member) { + return command(cmd::zrem, key, member); + } + + template + QueuedRedis& zrem(const StringView &key, Input first, Input last) { + return command(cmd::zrem_range, key, first, last); + } + + template + QueuedRedis& zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + QueuedRedis& zremrangebylex(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebylex, key, interval); + } + + QueuedRedis& zremrangebyrank(const StringView &key, long long start, long long stop) { + return command(cmd::zremrangebyrank, key, start, stop); + } + + template + QueuedRedis& zremrangebyscore(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebyscore, key, interval); + } + + // See comments on *ZRANGE*. + QueuedRedis& zrevrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrevrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrevrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, const Interval &interval) { + return zrevrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrevrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrevrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrevrank(const StringView &key, const StringView &member) { + return command(cmd::zrevrank, key, member); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::zscan, key, cursor, pattern, count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return zscan(key, cursor, pattern, 10); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + long long count) { + return zscan(key, cursor, "*", count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor) { + return zscan(key, cursor, "*", 10); + } + + QueuedRedis& zscore(const StringView &key, const StringView &member) { + return command(cmd::zscore, key, member); + } + + QueuedRedis& zunionstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zunionstore, destination, key, weight); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + return command(cmd::zunionstore_range, destination, first, last, type); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + QueuedRedis& pfadd(const StringView &key, const StringView &element) { + return command(cmd::pfadd, key, element); + } + + template + QueuedRedis& pfadd(const StringView &key, Input first, Input last) { + return command(cmd::pfadd_range, key, first, last); + } + + template + QueuedRedis& pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + QueuedRedis& pfcount(const StringView &key) { + return command(cmd::pfcount, key); + } + + template + QueuedRedis& pfcount(Input first, Input last) { + return command(cmd::pfcount_range, first, last); + } + + template + QueuedRedis& pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + QueuedRedis& pfmerge(const StringView &destination, const StringView &key) { + return command(cmd::pfmerge, destination, key); + } + + template + QueuedRedis& pfmerge(const StringView &destination, Input first, Input last) { + return command(cmd::pfmerge_range, destination, first, last); + } + + template + QueuedRedis& pfmerge(const StringView &destination, std::initializer_list il) { + return pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + QueuedRedis& geoadd(const StringView &key, + const std::tuple &member) { + return command(cmd::geoadd, key, member); + } + + template + QueuedRedis& geoadd(const StringView &key, + Input first, + Input last) { + return command(cmd::geoadd_range, key, first, last); + } + + template + QueuedRedis& geoadd(const StringView &key, std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + QueuedRedis& geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M) { + return command(cmd::geodist, key, member1, member2, unit); + } + + template + QueuedRedis& geohash(const StringView &key, Input first, Input last) { + return command(cmd::geohash_range, key, first, last); + } + + template + QueuedRedis& geohash(const StringView &key, std::initializer_list il) { + return geohash(key, il.begin(), il.end()); + } + + template + QueuedRedis& geopos(const StringView &key, Input first, Input last) { + return command(cmd::geopos_range, key, first, last); + } + + template + QueuedRedis& geopos(const StringView &key, std::initializer_list il) { + return geopos(key, il.begin(), il.end()); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _georadius_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadius_store, + key, + loc, + radius, + unit, + destination, + store_dist, + count); + } + + // NOTE: *QueuedRedis::georadius*'s parameters are different from *Redis::georadius*. + // *Redis::georadius* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::georadius*. So we have to use extra parameters to decide + // whether we should send options to Redis. This also applies to *GEORADIUSBYMEMBER*. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _georadius_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + destination, + store_dist, + count); + } + + // See the comments on *GEORADIUS*. + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + // SCRIPTING commands. + + QueuedRedis& eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return command(cmd::eval, script, keys, args); + } + + QueuedRedis& evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return command(cmd::evalsha, script, keys, args); + } + + template + QueuedRedis& script_exists(Input first, Input last) { + return command(cmd::script_exists_range, first, last); + } + + template + QueuedRedis& script_exists(std::initializer_list il) { + return script_exists(il.begin(), il.end()); + } + + QueuedRedis& script_flush() { + return command(cmd::script_flush); + } + + QueuedRedis& script_kill() { + return command(cmd::script_kill); + } + + QueuedRedis& script_load(const StringView &script) { + return command(cmd::script_load, script); + } + + // PUBSUB commands. + + QueuedRedis& publish(const StringView &channel, const StringView &message) { + return command(cmd::publish, channel, message); + } + + // Stream commands. + + QueuedRedis& xack(const StringView &key, const StringView &group, const StringView &id) { + return command(cmd::xack, key, group, id); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, Input first, Input last) { + return command(cmd::xack_range, key, group, first, last); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, Input first, Input last) { + return command(cmd::xadd_range, key, id, first, last); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true) { + return command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id) { + return command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last) { + return command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il) { + return xclaim(key, group, consumer, min_idle_time, il.begin(), il.end()); + } + + QueuedRedis& xdel(const StringView &key, const StringView &id) { + return command(cmd::xdel, key, id); + } + + template + QueuedRedis& xdel(const StringView &key, Input first, Input last) { + return command(cmd::xdel_range, key, first, last); + } + + template + QueuedRedis& xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + QueuedRedis& xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false) { + return command(cmd::xgroup_create, key, group, id, mkstream); + } + + QueuedRedis& xgroup_setid(const StringView &key, + const StringView &group, + const StringView &id) { + return command(cmd::xgroup_setid, key, group, id); + } + + QueuedRedis& xgroup_destroy(const StringView &key, const StringView &group) { + return command(cmd::xgroup_destroy, key, group); + } + + QueuedRedis& xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer) { + return command(cmd::xgroup_delconsumer, key, group, consumer); + } + + QueuedRedis& xlen(const StringView &key) { + return command(cmd::xlen, key); + } + + QueuedRedis& xpending(const StringView &key, const StringView &group) { + return command(cmd::xpending, key, group); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xpending_detail, key, group, start, end, count); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + return command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end) { + return command(cmd::xrange, key, start, end); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xrange, key, start, end, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id, long long count) { + return command(cmd::xread, key, id, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id) { + return xread(key, id, 0); + } + + template + auto xread(Input first, Input last, long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xread_range, first, last, count); + } + + template + auto xread(Input first, Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, 0); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return command(cmd::xread_block, key, id, timeout.count(), count); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xread(key, id, timeout, 0); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xread_block_range, first, last, timeout.count(), count); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, timeout, 0); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + return command(cmd::xreadgroup, group, consumer, key, id, count, noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count) { + return xreadgroup(group, consumer, key, id, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, 0, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) { + return command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return xreadgroup(group, consumer, key, id, timeout, count, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xreadgroup(group, consumer, key, id, timeout, 0, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, 0, false); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start) { + return command(cmd::xrevrange, key, end, start); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + return command(cmd::xrevrange, key, end, start, count); + } + + QueuedRedis& xtrim(const StringView &key, long long count, bool approx = true) { + return command(cmd::xtrim, key, count, approx); + } + +private: + friend class Redis; + + friend class RedisCluster; + + template + QueuedRedis(const ConnectionSPtr &connection, Args &&...args); + + void _sanity_check() const; + + void _reset(); + + void _invalidate(); + + void _rewrite_replies(std::vector &replies) const; + + template + void _rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const; + + ConnectionSPtr _connection; + + Impl _impl; + + std::size_t _cmd_num = 0; + + std::vector _set_cmd_indexes; + + std::vector _georadius_cmd_indexes; + + bool _valid = true; +}; + +class QueuedReplies { +public: + std::size_t size() const; + + redisReply& get(std::size_t idx); + + template + Result get(std::size_t idx); + + template + void get(std::size_t idx, Output output); + +private: + template + friend class QueuedRedis; + + explicit QueuedReplies(std::vector replies) : _replies(std::move(replies)) {} + + void _index_check(std::size_t idx) const; + + std::vector _replies; +}; + +} + +} + +#include "queued_redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.hpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.hpp new file mode 100644 index 000000000..409f48aca --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.hpp @@ -0,0 +1,208 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP + +namespace sw { + +namespace redis { + +template +template +QueuedRedis::QueuedRedis(const ConnectionSPtr &connection, Args &&...args) : + _connection(connection), + _impl(std::forward(args)...) { + assert(_connection); +} + +template +Redis QueuedRedis::redis() { + return Redis(_connection); +} + +template +template +auto QueuedRedis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type { + try { + _sanity_check(); + + _impl.command(*_connection, cmd, std::forward(args)...); + + ++_cmd_num; + } catch (const Error &e) { + _invalidate(); + throw; + } + + return *this; +} + +template +template +QueuedRedis& QueuedRedis::command(const StringView &cmd_name, Args &&...args) { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +template +auto QueuedRedis::command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type { + if (first == last) { + throw Error("command: empty range"); + } + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +QueuedReplies QueuedRedis::exec() { + try { + _sanity_check(); + + auto replies = _impl.exec(*_connection, _cmd_num); + + _rewrite_replies(replies); + + _reset(); + + return QueuedReplies(std::move(replies)); + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +void QueuedRedis::discard() { + try { + _sanity_check(); + + _impl.discard(*_connection, _cmd_num); + + _reset(); + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +void QueuedRedis::_sanity_check() const { + if (!_valid) { + throw Error("Not in valid state"); + } + + if (_connection->broken()) { + throw Error("Connection is broken"); + } +} + +template +inline void QueuedRedis::_reset() { + _cmd_num = 0; + + _set_cmd_indexes.clear(); + + _georadius_cmd_indexes.clear(); +} + +template +void QueuedRedis::_invalidate() { + _valid = false; + + _reset(); +} + +template +void QueuedRedis::_rewrite_replies(std::vector &replies) const { + _rewrite_replies(_set_cmd_indexes, reply::rewrite_set_reply, replies); + + _rewrite_replies(_georadius_cmd_indexes, reply::rewrite_georadius_reply, replies); +} + +template +template +void QueuedRedis::_rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const { + for (auto idx : indexes) { + assert(idx < replies.size()); + + auto &reply = replies[idx]; + + assert(reply); + + rewriter(*reply); + } +} + +inline std::size_t QueuedReplies::size() const { + return _replies.size(); +} + +inline redisReply& QueuedReplies::get(std::size_t idx) { + _index_check(idx); + + auto &reply = _replies[idx]; + + assert(reply); + + return *reply; +} + +template +inline Result QueuedReplies::get(std::size_t idx) { + auto &reply = get(idx); + + return reply::parse(reply); +} + +template +inline void QueuedReplies::get(std::size_t idx, Output output) { + auto &reply = get(idx); + + reply::to_array(reply, output); +} + +inline void QueuedReplies::_index_check(std::size_t idx) const { + if (idx >= size()) { + throw Error("Out of range"); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis++.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis++.h new file mode 100644 index 000000000..0da0ebb16 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis++.h @@ -0,0 +1,25 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H +#define SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H + +#include "redis.h" +#include "redis_cluster.h" +#include "queued_redis.h" +#include "sentinel.h" + +#endif // end SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.cpp new file mode 100644 index 000000000..be96967fe --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.cpp @@ -0,0 +1,882 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "redis.h" +#include +#include "command.h" +#include "errors.h" +#include "queued_redis.h" + +namespace sw { + +namespace redis { + +Redis::Redis(const std::string &uri) : Redis(ConnectionOptions(uri)) {} + +Redis::Redis(const ConnectionSPtr &connection) : _connection(connection) { + assert(_connection); +} + +Pipeline Redis::pipeline() { + return Pipeline(std::make_shared(_pool.create())); +} + +Transaction Redis::transaction(bool piped) { + return Transaction(std::make_shared(_pool.create()), piped); +} + +Subscriber Redis::subscriber() { + return Subscriber(_pool.create()); +} + +// CONNECTION commands. + +void Redis::auth(const StringView &password) { + auto reply = command(cmd::auth, password); + + reply::parse(*reply); +} + +std::string Redis::echo(const StringView &msg) { + auto reply = command(cmd::echo, msg); + + return reply::parse(*reply); +} + +std::string Redis::ping() { + auto reply = command(cmd::ping); + + return reply::to_status(*reply); +} + +std::string Redis::ping(const StringView &msg) { + auto reply = command(cmd::ping, msg); + + return reply::parse(*reply); +} + +void Redis::swapdb(long long idx1, long long idx2) { + auto reply = command(cmd::swapdb, idx1, idx2); + + reply::parse(*reply); +} + +// SERVER commands. + +void Redis::bgrewriteaof() { + auto reply = command(cmd::bgrewriteaof); + + reply::parse(*reply); +} + +void Redis::bgsave() { + auto reply = command(cmd::bgsave); + + reply::parse(*reply); +} + +long long Redis::dbsize() { + auto reply = command(cmd::dbsize); + + return reply::parse(*reply); +} + +void Redis::flushall(bool async) { + auto reply = command(cmd::flushall, async); + + reply::parse(*reply); +} + +void Redis::flushdb(bool async) { + auto reply = command(cmd::flushdb, async); + + reply::parse(*reply); +} + +std::string Redis::info() { + auto reply = command(cmd::info); + + return reply::parse(*reply); +} + +std::string Redis::info(const StringView §ion) { + auto reply = command(cmd::info, section); + + return reply::parse(*reply); +} + +long long Redis::lastsave() { + auto reply = command(cmd::lastsave); + + return reply::parse(*reply); +} + +void Redis::save() { + auto reply = command(cmd::save); + + reply::parse(*reply); +} + +// KEY commands. + +long long Redis::del(const StringView &key) { + auto reply = command(cmd::del, key); + + return reply::parse(*reply); +} + +OptionalString Redis::dump(const StringView &key) { + auto reply = command(cmd::dump, key); + + return reply::parse(*reply); +} + +long long Redis::exists(const StringView &key) { + auto reply = command(cmd::exists, key); + + return reply::parse(*reply); +} + +bool Redis::expire(const StringView &key, long long timeout) { + auto reply = command(cmd::expire, key, timeout); + + return reply::parse(*reply); +} + +bool Redis::expireat(const StringView &key, long long timestamp) { + auto reply = command(cmd::expireat, key, timestamp); + + return reply::parse(*reply); +} + +bool Redis::move(const StringView &key, long long db) { + auto reply = command(cmd::move, key, db); + + return reply::parse(*reply); +} + +bool Redis::persist(const StringView &key) { + auto reply = command(cmd::persist, key); + + return reply::parse(*reply); +} + +bool Redis::pexpire(const StringView &key, long long timeout) { + auto reply = command(cmd::pexpire, key, timeout); + + return reply::parse(*reply); +} + +bool Redis::pexpireat(const StringView &key, long long timestamp) { + auto reply = command(cmd::pexpireat, key, timestamp); + + return reply::parse(*reply); +} + +long long Redis::pttl(const StringView &key) { + auto reply = command(cmd::pttl, key); + + return reply::parse(*reply); +} + +OptionalString Redis::randomkey() { + auto reply = command(cmd::randomkey); + + return reply::parse(*reply); +} + +void Redis::rename(const StringView &key, const StringView &newkey) { + auto reply = command(cmd::rename, key, newkey); + + reply::parse(*reply); +} + +bool Redis::renamenx(const StringView &key, const StringView &newkey) { + auto reply = command(cmd::renamenx, key, newkey); + + return reply::parse(*reply); +} + +void Redis::restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace) { + auto reply = command(cmd::restore, key, val, ttl, replace); + + reply::parse(*reply); +} + +long long Redis::touch(const StringView &key) { + auto reply = command(cmd::touch, key); + + return reply::parse(*reply); +} + +long long Redis::ttl(const StringView &key) { + auto reply = command(cmd::ttl, key); + + return reply::parse(*reply); +} + +std::string Redis::type(const StringView &key) { + auto reply = command(cmd::type, key); + + return reply::parse(*reply); +} + +long long Redis::unlink(const StringView &key) { + auto reply = command(cmd::unlink, key); + + return reply::parse(*reply); +} + +long long Redis::wait(long long numslaves, long long timeout) { + auto reply = command(cmd::wait, numslaves, timeout); + + return reply::parse(*reply); +} + +// STRING commands. + +long long Redis::append(const StringView &key, const StringView &val) { + auto reply = command(cmd::append, key, val); + + return reply::parse(*reply); +} + +long long Redis::bitcount(const StringView &key, long long start, long long end) { + auto reply = command(cmd::bitcount, key, start, end); + + return reply::parse(*reply); +} + +long long Redis::bitop(BitOp op, const StringView &destination, const StringView &key) { + auto reply = command(cmd::bitop, op, destination, key); + + return reply::parse(*reply); +} + +long long Redis::bitpos(const StringView &key, + long long bit, + long long start, + long long end) { + auto reply = command(cmd::bitpos, key, bit, start, end); + + return reply::parse(*reply); +} + +long long Redis::decr(const StringView &key) { + auto reply = command(cmd::decr, key); + + return reply::parse(*reply); +} + +long long Redis::decrby(const StringView &key, long long decrement) { + auto reply = command(cmd::decrby, key, decrement); + + return reply::parse(*reply); +} + +OptionalString Redis::get(const StringView &key) { + auto reply = command(cmd::get, key); + + return reply::parse(*reply); +} + +long long Redis::getbit(const StringView &key, long long offset) { + auto reply = command(cmd::getbit, key, offset); + + return reply::parse(*reply); +} + +std::string Redis::getrange(const StringView &key, long long start, long long end) { + auto reply = command(cmd::getrange, key, start, end); + + return reply::parse(*reply); +} + +OptionalString Redis::getset(const StringView &key, const StringView &val) { + auto reply = command(cmd::getset, key, val); + + return reply::parse(*reply); +} + +long long Redis::incr(const StringView &key) { + auto reply = command(cmd::incr, key); + + return reply::parse(*reply); +} + +long long Redis::incrby(const StringView &key, long long increment) { + auto reply = command(cmd::incrby, key, increment); + + return reply::parse(*reply); +} + +double Redis::incrbyfloat(const StringView &key, double increment) { + auto reply = command(cmd::incrbyfloat, key, increment); + + return reply::parse(*reply); +} + +void Redis::psetex(const StringView &key, + long long ttl, + const StringView &val) { + auto reply = command(cmd::psetex, key, ttl, val); + + reply::parse(*reply); +} + +bool Redis::set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + UpdateType type) { + auto reply = command(cmd::set, key, val, ttl.count(), type); + + reply::rewrite_set_reply(*reply); + + return reply::parse(*reply); +} + +void Redis::setex(const StringView &key, + long long ttl, + const StringView &val) { + auto reply = command(cmd::setex, key, ttl, val); + + reply::parse(*reply); +} + +bool Redis::setnx(const StringView &key, const StringView &val) { + auto reply = command(cmd::setnx, key, val); + + return reply::parse(*reply); +} + +long long Redis::setrange(const StringView &key, long long offset, const StringView &val) { + auto reply = command(cmd::setrange, key, offset, val); + + return reply::parse(*reply); +} + +long long Redis::strlen(const StringView &key) { + auto reply = command(cmd::strlen, key); + + return reply::parse(*reply); +} + +// LIST commands. + +OptionalStringPair Redis::blpop(const StringView &key, long long timeout) { + auto reply = command(cmd::blpop, key, timeout); + + return reply::parse(*reply); +} + +OptionalStringPair Redis::blpop(const StringView &key, const std::chrono::seconds &timeout) { + return blpop(key, timeout.count()); +} + +OptionalStringPair Redis::brpop(const StringView &key, long long timeout) { + auto reply = command(cmd::brpop, key, timeout); + + return reply::parse(*reply); +} + +OptionalStringPair Redis::brpop(const StringView &key, const std::chrono::seconds &timeout) { + return brpop(key, timeout.count()); +} + +OptionalString Redis::brpoplpush(const StringView &source, + const StringView &destination, + long long timeout) { + auto reply = command(cmd::brpoplpush, source, destination, timeout); + + return reply::parse(*reply); +} + +OptionalString Redis::lindex(const StringView &key, long long index) { + auto reply = command(cmd::lindex, key, index); + + return reply::parse(*reply); +} + +long long Redis::linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val) { + auto reply = command(cmd::linsert, key, position, pivot, val); + + return reply::parse(*reply); +} + +long long Redis::llen(const StringView &key) { + auto reply = command(cmd::llen, key); + + return reply::parse(*reply); +} + +OptionalString Redis::lpop(const StringView &key) { + auto reply = command(cmd::lpop, key); + + return reply::parse(*reply); +} + +long long Redis::lpush(const StringView &key, const StringView &val) { + auto reply = command(cmd::lpush, key, val); + + return reply::parse(*reply); +} + +long long Redis::lpushx(const StringView &key, const StringView &val) { + auto reply = command(cmd::lpushx, key, val); + + return reply::parse(*reply); +} + +long long Redis::lrem(const StringView &key, long long count, const StringView &val) { + auto reply = command(cmd::lrem, key, count, val); + + return reply::parse(*reply); +} + +void Redis::lset(const StringView &key, long long index, const StringView &val) { + auto reply = command(cmd::lset, key, index, val); + + reply::parse(*reply); +} + +void Redis::ltrim(const StringView &key, long long start, long long stop) { + auto reply = command(cmd::ltrim, key, start, stop); + + reply::parse(*reply); +} + +OptionalString Redis::rpop(const StringView &key) { + auto reply = command(cmd::rpop, key); + + return reply::parse(*reply); +} + +OptionalString Redis::rpoplpush(const StringView &source, const StringView &destination) { + auto reply = command(cmd::rpoplpush, source, destination); + + return reply::parse(*reply); +} + +long long Redis::rpush(const StringView &key, const StringView &val) { + auto reply = command(cmd::rpush, key, val); + + return reply::parse(*reply); +} + +long long Redis::rpushx(const StringView &key, const StringView &val) { + auto reply = command(cmd::rpushx, key, val); + + return reply::parse(*reply); +} + +long long Redis::hdel(const StringView &key, const StringView &field) { + auto reply = command(cmd::hdel, key, field); + + return reply::parse(*reply); +} + +bool Redis::hexists(const StringView &key, const StringView &field) { + auto reply = command(cmd::hexists, key, field); + + return reply::parse(*reply); +} + +OptionalString Redis::hget(const StringView &key, const StringView &field) { + auto reply = command(cmd::hget, key, field); + + return reply::parse(*reply); +} + +long long Redis::hincrby(const StringView &key, const StringView &field, long long increment) { + auto reply = command(cmd::hincrby, key, field, increment); + + return reply::parse(*reply); +} + +double Redis::hincrbyfloat(const StringView &key, const StringView &field, double increment) { + auto reply = command(cmd::hincrbyfloat, key, field, increment); + + return reply::parse(*reply); +} + +long long Redis::hlen(const StringView &key) { + auto reply = command(cmd::hlen, key); + + return reply::parse(*reply); +} + +bool Redis::hset(const StringView &key, const StringView &field, const StringView &val) { + auto reply = command(cmd::hset, key, field, val); + + return reply::parse(*reply); +} + +bool Redis::hset(const StringView &key, const std::pair &item) { + return hset(key, item.first, item.second); +} + +bool Redis::hsetnx(const StringView &key, const StringView &field, const StringView &val) { + auto reply = command(cmd::hsetnx, key, field, val); + + return reply::parse(*reply); +} + +bool Redis::hsetnx(const StringView &key, const std::pair &item) { + return hsetnx(key, item.first, item.second); +} + +long long Redis::hstrlen(const StringView &key, const StringView &field) { + auto reply = command(cmd::hstrlen, key, field); + + return reply::parse(*reply); +} + +// SET commands. + +long long Redis::sadd(const StringView &key, const StringView &member) { + auto reply = command(cmd::sadd, key, member); + + return reply::parse(*reply); +} + +long long Redis::scard(const StringView &key) { + auto reply = command(cmd::scard, key); + + return reply::parse(*reply); +} + +long long Redis::sdiffstore(const StringView &destination, const StringView &key) { + auto reply = command(cmd::sdiffstore, destination, key); + + return reply::parse(*reply); +} + +long long Redis::sinterstore(const StringView &destination, const StringView &key) { + auto reply = command(cmd::sinterstore, destination, key); + + return reply::parse(*reply); +} + +bool Redis::sismember(const StringView &key, const StringView &member) { + auto reply = command(cmd::sismember, key, member); + + return reply::parse(*reply); +} + +bool Redis::smove(const StringView &source, + const StringView &destination, + const StringView &member) { + auto reply = command(cmd::smove, source, destination, member); + + return reply::parse(*reply); +} + +OptionalString Redis::spop(const StringView &key) { + auto reply = command(cmd::spop, key); + + return reply::parse(*reply); +} + +OptionalString Redis::srandmember(const StringView &key) { + auto reply = command(cmd::srandmember, key); + + return reply::parse(*reply); +} + +long long Redis::srem(const StringView &key, const StringView &member) { + auto reply = command(cmd::srem, key, member); + + return reply::parse(*reply); +} + +long long Redis::sunionstore(const StringView &destination, const StringView &key) { + auto reply = command(cmd::sunionstore, destination, key); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +auto Redis::bzpopmax(const StringView &key, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax, key, timeout); + + return reply::parse>>(*reply); +} + +auto Redis::bzpopmin(const StringView &key, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin, key, timeout); + + return reply::parse>>(*reply); +} + +long long Redis::zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type, + bool changed) { + auto reply = command(cmd::zadd, key, member, score, type, changed); + + return reply::parse(*reply); +} + +long long Redis::zcard(const StringView &key) { + auto reply = command(cmd::zcard, key); + + return reply::parse(*reply); +} + +double Redis::zincrby(const StringView &key, double increment, const StringView &member) { + auto reply = command(cmd::zincrby, key, increment, member); + + return reply::parse(*reply); +} + +long long Redis::zinterstore(const StringView &destination, const StringView &key, double weight) { + auto reply = command(cmd::zinterstore, destination, key, weight); + + return reply::parse(*reply); +} + +Optional> Redis::zpopmax(const StringView &key) { + auto reply = command(cmd::zpopmax, key, 1); + + return reply::parse>>(*reply); +} + +Optional> Redis::zpopmin(const StringView &key) { + auto reply = command(cmd::zpopmin, key, 1); + + return reply::parse>>(*reply); +} + +OptionalLongLong Redis::zrank(const StringView &key, const StringView &member) { + auto reply = command(cmd::zrank, key, member); + + return reply::parse(*reply); +} + +long long Redis::zrem(const StringView &key, const StringView &member) { + auto reply = command(cmd::zrem, key, member); + + return reply::parse(*reply); +} + +long long Redis::zremrangebyrank(const StringView &key, long long start, long long stop) { + auto reply = command(cmd::zremrangebyrank, key, start, stop); + + return reply::parse(*reply); +} + +OptionalLongLong Redis::zrevrank(const StringView &key, const StringView &member) { + auto reply = command(cmd::zrevrank, key, member); + + return reply::parse(*reply); +} + +OptionalDouble Redis::zscore(const StringView &key, const StringView &member) { + auto reply = command(cmd::zscore, key, member); + + return reply::parse(*reply); +} + +long long Redis::zunionstore(const StringView &destination, const StringView &key, double weight) { + auto reply = command(cmd::zunionstore, destination, key, weight); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +bool Redis::pfadd(const StringView &key, const StringView &element) { + auto reply = command(cmd::pfadd, key, element); + + return reply::parse(*reply); +} + +long long Redis::pfcount(const StringView &key) { + auto reply = command(cmd::pfcount, key); + + return reply::parse(*reply); +} + +void Redis::pfmerge(const StringView &destination, const StringView &key) { + auto reply = command(cmd::pfmerge, destination, key); + + reply::parse(*reply); +} + +// GEO commands. + +long long Redis::geoadd(const StringView &key, + const std::tuple &member) { + auto reply = command(cmd::geoadd, key, member); + + return reply::parse(*reply); +} + +OptionalDouble Redis::geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit) { + auto reply = command(cmd::geodist, key, member1, member2, unit); + + return reply::parse(*reply); +} + +OptionalLongLong Redis::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + auto reply = command(cmd::georadius_store, + key, + loc, + radius, + unit, + destination, + store_dist, + count); + + reply::rewrite_georadius_reply(*reply); + + return reply::parse(*reply); +} + +OptionalLongLong Redis::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + auto reply = command(cmd::georadiusbymember_store, + key, + member, + radius, + unit, + destination, + store_dist, + count); + + reply::rewrite_georadius_reply(*reply); + + return reply::parse(*reply); +} + +// SCRIPTING commands. + +void Redis::script_flush() { + auto reply = command(cmd::script_flush); + + reply::parse(*reply); +} + +void Redis::script_kill() { + auto reply = command(cmd::script_kill); + + reply::parse(*reply); +} + +std::string Redis::script_load(const StringView &script) { + auto reply = command(cmd::script_load, script); + + return reply::parse(*reply); +} + +// PUBSUB commands. + +long long Redis::publish(const StringView &channel, const StringView &message) { + auto reply = command(cmd::publish, channel, message); + + return reply::parse(*reply); +} + +// Transaction commands. + +void Redis::watch(const StringView &key) { + auto reply = command(cmd::watch, key); + + reply::parse(*reply); +} + +// Stream commands. + +long long Redis::xack(const StringView &key, const StringView &group, const StringView &id) { + auto reply = command(cmd::xack, key, group, id); + + return reply::parse(*reply); +} + +long long Redis::xdel(const StringView &key, const StringView &id) { + auto reply = command(cmd::xdel, key, id); + + return reply::parse(*reply); +} + +void Redis::xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream) { + auto reply = command(cmd::xgroup_create, key, group, id, mkstream); + + reply::parse(*reply); +} + +void Redis::xgroup_setid(const StringView &key, const StringView &group, const StringView &id) { + auto reply = command(cmd::xgroup_setid, key, group, id); + + reply::parse(*reply); +} + +long long Redis::xgroup_destroy(const StringView &key, const StringView &group) { + auto reply = command(cmd::xgroup_destroy, key, group); + + return reply::parse(*reply); +} + +long long Redis::xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer) { + auto reply = command(cmd::xgroup_delconsumer, key, group, consumer); + + return reply::parse(*reply); +} + +long long Redis::xlen(const StringView &key) { + auto reply = command(cmd::xlen, key); + + return reply::parse(*reply); +} + +long long Redis::xtrim(const StringView &key, long long count, bool approx) { + auto reply = command(cmd::xtrim, key, count, approx); + + return reply::parse(*reply); +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.h new file mode 100644 index 000000000..b54afb96b --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.h @@ -0,0 +1,1523 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_H +#define SEWENEW_REDISPLUSPLUS_REDIS_H + +#include +#include +#include +#include +#include +#include "connection_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class Redis { +public: + Redis(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : _pool(pool_opts, connection_opts) {} + + // Construct Redis instance with URI: + // "tcp://127.0.0.1", "tcp://127.0.0.1:6379", or "unix://path/to/socket" + explicit Redis(const std::string &uri); + + Redis(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role, + const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(SimpleSentinel(sentinel, master_name, role), pool_opts, connection_opts) {} + + Redis(const Redis &) = delete; + Redis& operator=(const Redis &) = delete; + + Redis(Redis &&) = default; + Redis& operator=(Redis &&) = default; + + Pipeline pipeline(); + + Transaction transaction(bool piped = false); + + Subscriber subscriber(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type; + + template + Result command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // CONNECTION commands. + + void auth(const StringView &password); + + std::string echo(const StringView &msg); + + std::string ping(); + + std::string ping(const StringView &msg); + + // After sending QUIT, only the current connection will be close, while + // other connections in the pool is still open. This is a strange behavior. + // So we DO NOT support the QUIT command. If you want to quit connection to + // server, just destroy the Redis object. + // + // void quit(); + + // We get a connection from the pool, and send the SELECT command to switch + // to a specified DB. However, when we try to send other commands to the + // given DB, we might get a different connection from the pool, and these + // commands, in fact, work on other DB. e.g. + // + // redis.select(1); // get a connection from the pool and switch to the 1th DB + // redis.get("key"); // might get another connection from the pool, + // // and try to get 'key' on the default DB + // + // Obviously, this is NOT what we expect. So we DO NOT support SELECT command. + // In order to select a DB, we can specify the DB index with the ConnectionOptions. + // + // However, since Pipeline and Transaction always send multiple commands on a + // single connection, these two classes have a *select* method. + // + // void select(long long idx); + + void swapdb(long long idx1, long long idx2); + + // SERVER commands. + + void bgrewriteaof(); + + void bgsave(); + + long long dbsize(); + + void flushall(bool async = false); + + void flushdb(bool async = false); + + std::string info(); + + std::string info(const StringView §ion); + + long long lastsave(); + + void save(); + + // KEY commands. + + long long del(const StringView &key); + + template + long long del(Input first, Input last); + + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + OptionalString dump(const StringView &key); + + long long exists(const StringView &key); + + template + long long exists(Input first, Input last); + + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + bool expire(const StringView &key, long long timeout); + + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + bool expireat(const StringView &key, long long timestamp); + + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + template + void keys(const StringView &pattern, Output output); + + bool move(const StringView &key, long long db); + + bool persist(const StringView &key); + + bool pexpire(const StringView &key, long long timeout); + + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + bool pexpireat(const StringView &key, long long timestamp); + + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + long long pttl(const StringView &key); + + OptionalString randomkey(); + + void rename(const StringView &key, const StringView &newkey); + + bool renamenx(const StringView &key, const StringView &newkey); + + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + template + long long scan(long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long scan(long long cursor, + Output output); + + template + long long scan(long long cursor, + const StringView &pattern, + Output output); + + template + long long scan(long long cursor, + long long count, + Output output); + + long long touch(const StringView &key); + + template + long long touch(Input first, Input last); + + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + long long ttl(const StringView &key); + + std::string type(const StringView &key); + + long long unlink(const StringView &key); + + template + long long unlink(Input first, Input last); + + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + long long wait(long long numslaves, long long timeout); + + long long wait(long long numslaves, const std::chrono::milliseconds &timeout); + + // STRING commands. + + long long append(const StringView &key, const StringView &str); + + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + long long decr(const StringView &key); + + long long decrby(const StringView &key, long long decrement); + + OptionalString get(const StringView &key); + + long long getbit(const StringView &key, long long offset); + + std::string getrange(const StringView &key, long long start, long long end); + + OptionalString getset(const StringView &key, const StringView &val); + + long long incr(const StringView &key); + + long long incrby(const StringView &key, long long increment); + + double incrbyfloat(const StringView &key, double increment); + + template + void mget(Input first, Input last, Output output); + + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + template + void mset(Input first, Input last); + + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + template + bool msetnx(Input first, Input last); + + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + void setex(const StringView &key, + long long ttl, + const StringView &val); + + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + bool setnx(const StringView &key, const StringView &val); + + long long setrange(const StringView &key, long long offset, const StringView &val); + + long long strlen(const StringView &key); + + // LIST commands. + + OptionalStringPair blpop(const StringView &key, long long timeout); + + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + OptionalStringPair brpop(const StringView &key, long long timeout); + + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + OptionalString lindex(const StringView &key, long long index); + + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + long long llen(const StringView &key); + + OptionalString lpop(const StringView &key); + + long long lpush(const StringView &key, const StringView &val); + + template + long long lpush(const StringView &key, Input first, Input last); + + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + long long lpushx(const StringView &key, const StringView &val); + + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + long long lrem(const StringView &key, long long count, const StringView &val); + + void lset(const StringView &key, long long index, const StringView &val); + + void ltrim(const StringView &key, long long start, long long stop); + + OptionalString rpop(const StringView &key); + + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + long long rpush(const StringView &key, const StringView &val); + + template + long long rpush(const StringView &key, Input first, Input last); + + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + long long hdel(const StringView &key, const StringView &field); + + template + long long hdel(const StringView &key, Input first, Input last); + + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + bool hexists(const StringView &key, const StringView &field); + + OptionalString hget(const StringView &key, const StringView &field); + + template + void hgetall(const StringView &key, Output output); + + long long hincrby(const StringView &key, const StringView &field, long long increment); + + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + template + void hkeys(const StringView &key, Output output); + + long long hlen(const StringView &key); + + template + void hmget(const StringView &key, Input first, Input last, Output output); + + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + template + void hmset(const StringView &key, Input first, Input last); + + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + bool hset(const StringView &key, const StringView &field, const StringView &val); + + bool hset(const StringView &key, const std::pair &item); + + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + bool hsetnx(const StringView &key, const std::pair &item); + + long long hstrlen(const StringView &key, const StringView &field); + + template + void hvals(const StringView &key, Output output); + + // SET commands. + + long long sadd(const StringView &key, const StringView &member); + + template + long long sadd(const StringView &key, Input first, Input last); + + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + long long scard(const StringView &key); + + template + void sdiff(Input first, Input last, Output output); + + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + long long sdiffstore(const StringView &destination, const StringView &key); + + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + void sinter(Input first, Input last, Output output); + + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + long long sinterstore(const StringView &destination, const StringView &key); + + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + bool sismember(const StringView &key, const StringView &member); + + template + void smembers(const StringView &key, Output output); + + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + OptionalString spop(const StringView &key); + + template + void spop(const StringView &key, long long count, Output output); + + OptionalString srandmember(const StringView &key); + + template + void srandmember(const StringView &key, long long count, Output output); + + long long srem(const StringView &key, const StringView &member); + + template + long long srem(const StringView &key, Input first, Input last); + + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + template + void sunion(Input first, Input last, Output output); + + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + long long sunionstore(const StringView &destination, const StringView &key); + + template + long long sunionstore(const StringView &destination, Input first, Input last); + + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + long long zcard(const StringView &key); + + template + long long zcount(const StringView &key, const Interval &interval); + + double zincrby(const StringView &key, double increment, const StringView &member); + + // There's no aggregation type parameter for single key overload, since these 3 types + // have the same effect. + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + // If *Input* is an iterator of a container of string, + // we use the default weight, i.e. 1, and send + // *ZINTERSTORE destination numkeys key [key ...] [AGGREGATE SUM|MIN|MAX]* command. + // If *Input* is an iterator of a container of pair, i.e. key-weight pair, + // we send the command with the given weights: + // *ZINTERSTORE destination numkeys key [key ...] + // [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]* + // + // The following code use the default weight: + // + // vector keys = {"k1", "k2", "k3"}; + // redis.zinterstore(destination, keys.begin(), keys.end()); + // + // On the other hand, the following code use the given weights: + // + // vector> keys_with_weights = {{"k1", 1}, {"k2", 2}, {"k3", 3}}; + // redis.zinterstore(destination, keys_with_weights.begin(), keys_with_weights.end()); + // + // NOTE: `keys_with_weights` can also be of type `unordered_map`. + // However, it will be slower than vector>, since we use + // `distance(first, last)` to calculate the *numkeys* parameter. + // + // This also applies to *ZUNIONSTORE* command. + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + long long zlexcount(const StringView &key, const Interval &interval); + + Optional> zpopmax(const StringView &key); + + template + void zpopmax(const StringView &key, long long count, Output output); + + Optional> zpopmin(const StringView &key); + + template + void zpopmin(const StringView &key, long long count, Output output); + + // If *output* is an iterator of a container of string, + // we send *ZRANGE key start stop* command. + // If it's an iterator of a container of pair, + // we send *ZRANGE key start stop WITHSCORES* command. + // + // The following code sends *ZRANGE* without the *WITHSCORES* option: + // + // vector result; + // redis.zrange("key", 0, -1, back_inserter(result)); + // + // On the other hand, the following code sends command with *WITHSCORES* option: + // + // unordered_map with_score; + // redis.zrange("key", 0, -1, inserter(with_score, with_score.end())); + // + // This also applies to other commands with the *WITHSCORES* option, + // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrank(const StringView &key, const StringView &member); + + long long zrem(const StringView &key, const StringView &member); + + template + long long zrem(const StringView &key, Input first, Input last); + + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + OptionalDouble zscore(const StringView &key, const StringView &member); + + // There's no aggregation type parameter for single key overload, since these 3 types + // have the same effect. + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + // See *zinterstore* comment for how to use this method. + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + bool pfadd(const StringView &key, const StringView &element); + + template + bool pfadd(const StringView &key, Input first, Input last); + + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + void script_exists(Input first, Input last, Output output); + + template + void script_exists(std::initializer_list il, Output output) { + script_exists(il.begin(), il.end(), output); + } + + void script_flush(); + + void script_kill(); + + std::string script_load(const StringView &script); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Transaction commands. + void watch(const StringView &key); + + template + void watch(Input first, Input last); + + template + void watch(std::initializer_list il) { + watch(il.begin(), il.end()); + } + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first ,last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + class ConnectionPoolGuard { + public: + ConnectionPoolGuard(ConnectionPool &pool, + Connection &connection) : _pool(pool), _connection(connection) {} + + ~ConnectionPoolGuard() { + _pool.release(std::move(_connection)); + } + + private: + ConnectionPool &_pool; + Connection &_connection; + }; + + template + friend class QueuedRedis; + + friend class RedisCluster; + + // For internal use. + explicit Redis(const ConnectionSPtr &connection); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _command(Connection &connection, Cmd cmd, Args &&...args); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + // Pool Mode. + // Public constructors create a *Redis* instance with a pool. + // In this case, *_connection* is a null pointer, and is never used. + ConnectionPool _pool; + + // Single Connection Mode. + // Private constructor creats a *Redis* instance with a single connection. + // This is used when we create Transaction, Pipeline and Subscriber. + // In this case, *_pool* is empty, and is never used. + ConnectionSPtr _connection; +}; + +} + +} + +#include "redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.hpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.hpp new file mode 100644 index 000000000..3a227a6f1 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.hpp @@ -0,0 +1,1365 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_HPP + +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +template +auto Redis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (_connection) { + // Single Connection Mode. + // TODO: In this case, should we reconnect? + if (_connection->broken()) { + throw Error("Connection is broken"); + } + + return _command(*_connection, cmd, std::forward(args)...); + } else { + // Pool Mode, i.e. get connection from pool. + auto connection = _pool.fetch(); + + assert(!connection.broken()); + + ConnectionPoolGuard guard(_pool, connection); + + return _command(connection, cmd, std::forward(args)...); + } +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, ReplyUPtr>::type { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (first == last) { + throw Error("command: empty range"); + } + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +Result Redis::command(const StringView &cmd_name, Args &&...args) { + auto r = command(cmd_name, std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long Redis::del(Input first, Input last) { + if (first == last) { + throw Error("DEL: no key specified"); + } + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::exists(Input first, Input last) { + if (first == last) { + throw Error("EXISTS: no key specified"); + } + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool Redis::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool Redis::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +template +void Redis::keys(const StringView &pattern, Output output) { + auto reply = command(cmd::keys, pattern); + + reply::to_array(*reply, output); +} + +inline bool Redis::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool Redis::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void Redis::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long Redis::scan(long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::scan, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::scan(long long cursor, + const StringView &pattern, + Output output) { + return scan(cursor, pattern, 10, output); +} + +template +inline long long Redis::scan(long long cursor, + long long count, + Output output) { + return scan(cursor, "*", count, output); +} + +template +inline long long Redis::scan(long long cursor, + Output output) { + return scan(cursor, "*", 10, output); +} + +template +long long Redis::touch(Input first, Input last) { + if (first == last) { + throw Error("TOUCH: no key specified"); + } + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::unlink(Input first, Input last) { + if (first == last) { + throw Error("UNLINK: no key specified"); + } + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +inline long long Redis::wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); +} + +// STRING commands. + +template +long long Redis::bitop(BitOp op, const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("BITOP: no key specified"); + } + + auto reply = command(cmd::bitop_range, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::mget(Input first, Input last, Output output) { + if (first == last) { + throw Error("MGET: no key specified"); + } + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::mset(Input first, Input last) { + if (first == last) { + throw Error("MSET: no key specified"); + } + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool Redis::msetnx(Input first, Input last) { + if (first == last) { + throw Error("MSETNX: no key specified"); + } + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void Redis::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void Redis::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair Redis::blpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BLPOP: no key specified"); + } + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair Redis::brpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BRPOP: no key specified"); + } + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString Redis::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long Redis::lpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("LPUSH: no key specified"); + } + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long Redis::rpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("RPUSH: no key specified"); + } + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long Redis::hdel(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HDEL: no key specified"); + } + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmget(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("HMGET: no key specified"); + } + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmset(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HMSET: no key specified"); + } + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +inline void Redis::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long Redis::sadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SADD: no key specified"); + } + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sdiff(Input first, Input last, Output output) { + if (first == last) { + throw Error("SDIFF: no key specified"); + } + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sdiffstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SDIFFSTORE: no key specified"); + } + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sinter(Input first, Input last, Output output) { + if (first == last) { + throw Error("SINTER: no key specified"); + } + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sinterstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SINTERSTORE: no key specified"); + } + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void Redis::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long Redis::srem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SREM: no key specified"); + } + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void Redis::sunion(Input first, Input last, Output output) { + if (first == last) { + throw Error("SUNION: no key specified"); + } + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sunionstore(const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("SUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto Redis::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto Redis::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto Redis::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto Redis::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long Redis::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + if (first == last) { + throw Error("ZADD: no key specified"); + } + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long Redis::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZINTERSTORE: no key specified"); + } + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long Redis::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void Redis::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zrem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("ZREM: no key specified"); + } + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long Redis::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool Redis::pfadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("PFADD: no key specified"); + } + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::pfcount(Input first, Input last) { + if (first == last) { + throw Error("PFCOUNT: no key specified"); + } + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void Redis::pfmerge(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("PFMERGE: no key specified"); + } + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long Redis::geoadd(const StringView &key, + Input first, + Input last) { + if (first == last) { + throw Error("GEOADD: no key specified"); + } + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::geohash(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOHASH: no key specified"); + } + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::geopos(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOPOS: no key specified"); + } + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void Redis::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + auto reply = command(cmd::eval, script, keys, args); + + return reply::parse(*reply); +} + +template +void Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + auto reply = command(cmd::eval, script, keys, args); + + reply::to_array(*reply, output); +} + +template +Result Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + auto reply = command(cmd::evalsha, script, keys, args); + + return reply::parse(*reply); +} + +template +void Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + auto reply = command(cmd::evalsha, script, keys, args); + + reply::to_array(*reply, output); +} + +template +void Redis::script_exists(Input first, Input last, Output output) { + if (first == last) { + throw Error("SCRIPT EXISTS: no key specified"); + } + + auto reply = command(cmd::script_exists_range, first, last); + + reply::to_array(*reply, output); +} + +// Transaction commands. + +template +void Redis::watch(Input first, Input last) { + auto reply = command(cmd::watch_range, first, last); + + reply::parse(*reply); +} + +// Stream commands. + +template +long long Redis::xack(const StringView &key, const StringView &group, Input first, Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, const StringView &id, Input first, Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long Redis::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto Redis::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +ReplyUPtr Redis::_command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + auto reply = connection.recv(); + + return reply; +} + +template +inline ReplyUPtr Redis::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr Redis::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr Redis::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_HPP diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.cpp new file mode 100644 index 000000000..6950f5730 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.cpp @@ -0,0 +1,769 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "redis_cluster.h" +#include +#include "command.h" +#include "errors.h" +#include "queued_redis.h" + +namespace sw { + +namespace redis { + +RedisCluster::RedisCluster(const std::string &uri) : RedisCluster(ConnectionOptions(uri)) {} + +Redis RedisCluster::redis(const StringView &hash_tag) { + auto opts = _pool.connection_options(hash_tag); + return Redis(std::make_shared(opts)); +} + +Pipeline RedisCluster::pipeline(const StringView &hash_tag) { + auto opts = _pool.connection_options(hash_tag); + return Pipeline(std::make_shared(opts)); +} + +Transaction RedisCluster::transaction(const StringView &hash_tag, bool piped) { + auto opts = _pool.connection_options(hash_tag); + return Transaction(std::make_shared(opts), piped); +} + +Subscriber RedisCluster::subscriber() { + auto opts = _pool.connection_options(); + return Subscriber(Connection(opts)); +} + +// KEY commands. + +long long RedisCluster::del(const StringView &key) { + auto reply = command(cmd::del, key); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::dump(const StringView &key) { + auto reply = command(cmd::dump, key); + + return reply::parse(*reply); +} + +long long RedisCluster::exists(const StringView &key) { + auto reply = command(cmd::exists, key); + + return reply::parse(*reply); +} + +bool RedisCluster::expire(const StringView &key, long long timeout) { + auto reply = command(cmd::expire, key, timeout); + + return reply::parse(*reply); +} + +bool RedisCluster::expireat(const StringView &key, long long timestamp) { + auto reply = command(cmd::expireat, key, timestamp); + + return reply::parse(*reply); +} + +bool RedisCluster::persist(const StringView &key) { + auto reply = command(cmd::persist, key); + + return reply::parse(*reply); +} + +bool RedisCluster::pexpire(const StringView &key, long long timeout) { + auto reply = command(cmd::pexpire, key, timeout); + + return reply::parse(*reply); +} + +bool RedisCluster::pexpireat(const StringView &key, long long timestamp) { + auto reply = command(cmd::pexpireat, key, timestamp); + + return reply::parse(*reply); +} + +long long RedisCluster::pttl(const StringView &key) { + auto reply = command(cmd::pttl, key); + + return reply::parse(*reply); +} + +void RedisCluster::rename(const StringView &key, const StringView &newkey) { + auto reply = command(cmd::rename, key, newkey); + + reply::parse(*reply); +} + +bool RedisCluster::renamenx(const StringView &key, const StringView &newkey) { + auto reply = command(cmd::renamenx, key, newkey); + + return reply::parse(*reply); +} + +void RedisCluster::restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace) { + auto reply = command(cmd::restore, key, val, ttl, replace); + + reply::parse(*reply); +} + +long long RedisCluster::touch(const StringView &key) { + auto reply = command(cmd::touch, key); + + return reply::parse(*reply); +} + +long long RedisCluster::ttl(const StringView &key) { + auto reply = command(cmd::ttl, key); + + return reply::parse(*reply); +} + +std::string RedisCluster::type(const StringView &key) { + auto reply = command(cmd::type, key); + + return reply::parse(*reply); +} + +long long RedisCluster::unlink(const StringView &key) { + auto reply = command(cmd::unlink, key); + + return reply::parse(*reply); +} + +// STRING commands. + +long long RedisCluster::append(const StringView &key, const StringView &val) { + auto reply = command(cmd::append, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::bitcount(const StringView &key, long long start, long long end) { + auto reply = command(cmd::bitcount, key, start, end); + + return reply::parse(*reply); +} + +long long RedisCluster::bitop(BitOp op, const StringView &destination, const StringView &key) { + auto reply = _command(cmd::bitop, destination, op, destination, key); + + return reply::parse(*reply); +} + +long long RedisCluster::bitpos(const StringView &key, + long long bit, + long long start, + long long end) { + auto reply = command(cmd::bitpos, key, bit, start, end); + + return reply::parse(*reply); +} + +long long RedisCluster::decr(const StringView &key) { + auto reply = command(cmd::decr, key); + + return reply::parse(*reply); +} + +long long RedisCluster::decrby(const StringView &key, long long decrement) { + auto reply = command(cmd::decrby, key, decrement); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::get(const StringView &key) { + auto reply = command(cmd::get, key); + + return reply::parse(*reply); +} + +long long RedisCluster::getbit(const StringView &key, long long offset) { + auto reply = command(cmd::getbit, key, offset); + + return reply::parse(*reply); +} + +std::string RedisCluster::getrange(const StringView &key, long long start, long long end) { + auto reply = command(cmd::getrange, key, start, end); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::getset(const StringView &key, const StringView &val) { + auto reply = command(cmd::getset, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::incr(const StringView &key) { + auto reply = command(cmd::incr, key); + + return reply::parse(*reply); +} + +long long RedisCluster::incrby(const StringView &key, long long increment) { + auto reply = command(cmd::incrby, key, increment); + + return reply::parse(*reply); +} + +double RedisCluster::incrbyfloat(const StringView &key, double increment) { + auto reply = command(cmd::incrbyfloat, key, increment); + + return reply::parse(*reply); +} + +void RedisCluster::psetex(const StringView &key, + long long ttl, + const StringView &val) { + auto reply = command(cmd::psetex, key, ttl, val); + + reply::parse(*reply); +} + +bool RedisCluster::set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + UpdateType type) { + auto reply = command(cmd::set, key, val, ttl.count(), type); + + reply::rewrite_set_reply(*reply); + + return reply::parse(*reply); +} + +void RedisCluster::setex(const StringView &key, + long long ttl, + const StringView &val) { + auto reply = command(cmd::setex, key, ttl, val); + + reply::parse(*reply); +} + +bool RedisCluster::setnx(const StringView &key, const StringView &val) { + auto reply = command(cmd::setnx, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::setrange(const StringView &key, long long offset, const StringView &val) { + auto reply = command(cmd::setrange, key, offset, val); + + return reply::parse(*reply); +} + +long long RedisCluster::strlen(const StringView &key) { + auto reply = command(cmd::strlen, key); + + return reply::parse(*reply); +} + +// LIST commands. + +OptionalStringPair RedisCluster::blpop(const StringView &key, long long timeout) { + auto reply = command(cmd::blpop, key, timeout); + + return reply::parse(*reply); +} + +OptionalStringPair RedisCluster::blpop(const StringView &key, const std::chrono::seconds &timeout) { + return blpop(key, timeout.count()); +} + +OptionalStringPair RedisCluster::brpop(const StringView &key, long long timeout) { + auto reply = command(cmd::brpop, key, timeout); + + return reply::parse(*reply); +} + +OptionalStringPair RedisCluster::brpop(const StringView &key, const std::chrono::seconds &timeout) { + return brpop(key, timeout.count()); +} + +OptionalString RedisCluster::brpoplpush(const StringView &source, + const StringView &destination, + long long timeout) { + auto reply = command(cmd::brpoplpush, source, destination, timeout); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::lindex(const StringView &key, long long index) { + auto reply = command(cmd::lindex, key, index); + + return reply::parse(*reply); +} + +long long RedisCluster::linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val) { + auto reply = command(cmd::linsert, key, position, pivot, val); + + return reply::parse(*reply); +} + +long long RedisCluster::llen(const StringView &key) { + auto reply = command(cmd::llen, key); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::lpop(const StringView &key) { + auto reply = command(cmd::lpop, key); + + return reply::parse(*reply); +} + +long long RedisCluster::lpush(const StringView &key, const StringView &val) { + auto reply = command(cmd::lpush, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::lpushx(const StringView &key, const StringView &val) { + auto reply = command(cmd::lpushx, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::lrem(const StringView &key, long long count, const StringView &val) { + auto reply = command(cmd::lrem, key, count, val); + + return reply::parse(*reply); +} + +void RedisCluster::lset(const StringView &key, long long index, const StringView &val) { + auto reply = command(cmd::lset, key, index, val); + + reply::parse(*reply); +} + +void RedisCluster::ltrim(const StringView &key, long long start, long long stop) { + auto reply = command(cmd::ltrim, key, start, stop); + + reply::parse(*reply); +} + +OptionalString RedisCluster::rpop(const StringView &key) { + auto reply = command(cmd::rpop, key); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::rpoplpush(const StringView &source, const StringView &destination) { + auto reply = command(cmd::rpoplpush, source, destination); + + return reply::parse(*reply); +} + +long long RedisCluster::rpush(const StringView &key, const StringView &val) { + auto reply = command(cmd::rpush, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::rpushx(const StringView &key, const StringView &val) { + auto reply = command(cmd::rpushx, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::hdel(const StringView &key, const StringView &field) { + auto reply = command(cmd::hdel, key, field); + + return reply::parse(*reply); +} + +bool RedisCluster::hexists(const StringView &key, const StringView &field) { + auto reply = command(cmd::hexists, key, field); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::hget(const StringView &key, const StringView &field) { + auto reply = command(cmd::hget, key, field); + + return reply::parse(*reply); +} + +long long RedisCluster::hincrby(const StringView &key, const StringView &field, long long increment) { + auto reply = command(cmd::hincrby, key, field, increment); + + return reply::parse(*reply); +} + +double RedisCluster::hincrbyfloat(const StringView &key, const StringView &field, double increment) { + auto reply = command(cmd::hincrbyfloat, key, field, increment); + + return reply::parse(*reply); +} + +long long RedisCluster::hlen(const StringView &key) { + auto reply = command(cmd::hlen, key); + + return reply::parse(*reply); +} + +bool RedisCluster::hset(const StringView &key, const StringView &field, const StringView &val) { + auto reply = command(cmd::hset, key, field, val); + + return reply::parse(*reply); +} + +bool RedisCluster::hset(const StringView &key, const std::pair &item) { + return hset(key, item.first, item.second); +} + +bool RedisCluster::hsetnx(const StringView &key, const StringView &field, const StringView &val) { + auto reply = command(cmd::hsetnx, key, field, val); + + return reply::parse(*reply); +} + +bool RedisCluster::hsetnx(const StringView &key, const std::pair &item) { + return hsetnx(key, item.first, item.second); +} + +long long RedisCluster::hstrlen(const StringView &key, const StringView &field) { + auto reply = command(cmd::hstrlen, key, field); + + return reply::parse(*reply); +} + +// SET commands. + +long long RedisCluster::sadd(const StringView &key, const StringView &member) { + auto reply = command(cmd::sadd, key, member); + + return reply::parse(*reply); +} + +long long RedisCluster::scard(const StringView &key) { + auto reply = command(cmd::scard, key); + + return reply::parse(*reply); +} + +long long RedisCluster::sdiffstore(const StringView &destination, const StringView &key) { + auto reply = command(cmd::sdiffstore, destination, key); + + return reply::parse(*reply); +} + +long long RedisCluster::sinterstore(const StringView &destination, const StringView &key) { + auto reply = command(cmd::sinterstore, destination, key); + + return reply::parse(*reply); +} + +bool RedisCluster::sismember(const StringView &key, const StringView &member) { + auto reply = command(cmd::sismember, key, member); + + return reply::parse(*reply); +} + +bool RedisCluster::smove(const StringView &source, + const StringView &destination, + const StringView &member) { + auto reply = command(cmd::smove, source, destination, member); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::spop(const StringView &key) { + auto reply = command(cmd::spop, key); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::srandmember(const StringView &key) { + auto reply = command(cmd::srandmember, key); + + return reply::parse(*reply); +} + +long long RedisCluster::srem(const StringView &key, const StringView &member) { + auto reply = command(cmd::srem, key, member); + + return reply::parse(*reply); +} + +long long RedisCluster::sunionstore(const StringView &destination, const StringView &key) { + auto reply = command(cmd::sunionstore, destination, key); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +auto RedisCluster::bzpopmax(const StringView &key, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax, key, timeout); + + return reply::parse>>(*reply); +} + +auto RedisCluster::bzpopmin(const StringView &key, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin, key, timeout); + + return reply::parse>>(*reply); +} + +long long RedisCluster::zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type, + bool changed) { + auto reply = command(cmd::zadd, key, member, score, type, changed); + + return reply::parse(*reply); +} + +long long RedisCluster::zcard(const StringView &key) { + auto reply = command(cmd::zcard, key); + + return reply::parse(*reply); +} + +double RedisCluster::zincrby(const StringView &key, double increment, const StringView &member) { + auto reply = command(cmd::zincrby, key, increment, member); + + return reply::parse(*reply); +} + +long long RedisCluster::zinterstore(const StringView &destination, + const StringView &key, + double weight) { + auto reply = command(cmd::zinterstore, destination, key, weight); + + return reply::parse(*reply); +} + +Optional> RedisCluster::zpopmax(const StringView &key) { + auto reply = command(cmd::zpopmax, key, 1); + + return reply::parse>>(*reply); +} + +Optional> RedisCluster::zpopmin(const StringView &key) { + auto reply = command(cmd::zpopmin, key, 1); + + return reply::parse>>(*reply); +} + +OptionalLongLong RedisCluster::zrank(const StringView &key, const StringView &member) { + auto reply = command(cmd::zrank, key, member); + + return reply::parse(*reply); +} + +long long RedisCluster::zrem(const StringView &key, const StringView &member) { + auto reply = command(cmd::zrem, key, member); + + return reply::parse(*reply); +} + +long long RedisCluster::zremrangebyrank(const StringView &key, long long start, long long stop) { + auto reply = command(cmd::zremrangebyrank, key, start, stop); + + return reply::parse(*reply); +} + +OptionalLongLong RedisCluster::zrevrank(const StringView &key, const StringView &member) { + auto reply = command(cmd::zrevrank, key, member); + + return reply::parse(*reply); +} + +OptionalDouble RedisCluster::zscore(const StringView &key, const StringView &member) { + auto reply = command(cmd::zscore, key, member); + + return reply::parse(*reply); +} + +long long RedisCluster::zunionstore(const StringView &destination, + const StringView &key, + double weight) { + auto reply = command(cmd::zunionstore, destination, key, weight); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +bool RedisCluster::pfadd(const StringView &key, const StringView &element) { + auto reply = command(cmd::pfadd, key, element); + + return reply::parse(*reply); +} + +long long RedisCluster::pfcount(const StringView &key) { + auto reply = command(cmd::pfcount, key); + + return reply::parse(*reply); +} + +void RedisCluster::pfmerge(const StringView &destination, const StringView &key) { + auto reply = command(cmd::pfmerge, destination, key); + + reply::parse(*reply); +} + +// GEO commands. + +long long RedisCluster::geoadd(const StringView &key, + const std::tuple &member) { + auto reply = command(cmd::geoadd, key, member); + + return reply::parse(*reply); +} + +OptionalDouble RedisCluster::geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit) { + auto reply = command(cmd::geodist, key, member1, member2, unit); + + return reply::parse(*reply); +} + +OptionalLongLong RedisCluster::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + auto reply = command(cmd::georadius_store, + key, + loc, + radius, + unit, + destination, + store_dist, + count); + + reply::rewrite_georadius_reply(*reply); + + return reply::parse(*reply); +} + +OptionalLongLong RedisCluster::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + auto reply = command(cmd::georadiusbymember_store, + key, + member, + radius, + unit, + destination, + store_dist, + count); + + reply::rewrite_georadius_reply(*reply); + + return reply::parse(*reply); +} + +// PUBSUB commands. + +long long RedisCluster::publish(const StringView &channel, const StringView &message) { + auto reply = command(cmd::publish, channel, message); + + return reply::parse(*reply); +} + +// Stream commands. + +long long RedisCluster::xack(const StringView &key, const StringView &group, const StringView &id) { + auto reply = command(cmd::xack, key, group, id); + + return reply::parse(*reply); +} + +long long RedisCluster::xdel(const StringView &key, const StringView &id) { + auto reply = command(cmd::xdel, key, id); + + return reply::parse(*reply); +} + +void RedisCluster::xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream) { + auto reply = command(cmd::xgroup_create, key, group, id, mkstream); + + reply::parse(*reply); +} + +void RedisCluster::xgroup_setid(const StringView &key, + const StringView &group, + const StringView &id) { + auto reply = command(cmd::xgroup_setid, key, group, id); + + reply::parse(*reply); +} + +long long RedisCluster::xgroup_destroy(const StringView &key, const StringView &group) { + auto reply = command(cmd::xgroup_destroy, key, group); + + return reply::parse(*reply); +} + +long long RedisCluster::xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer) { + auto reply = command(cmd::xgroup_delconsumer, key, group, consumer); + + return reply::parse(*reply); +} + +long long RedisCluster::xlen(const StringView &key) { + auto reply = command(cmd::xlen, key); + + return reply::parse(*reply); +} + +long long RedisCluster::xtrim(const StringView &key, long long count, bool approx) { + auto reply = command(cmd::xtrim, key, count, approx); + + return reply::parse(*reply); +} + +void RedisCluster::_asking(Connection &connection) { + // Send ASKING command. + connection.send("ASKING"); + + auto reply = connection.recv(); + + assert(reply); + + reply::parse(*reply); +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.h new file mode 100644 index 000000000..50a221367 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.h @@ -0,0 +1,1395 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H + +#include +#include +#include +#include +#include "shards_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "redis.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class RedisCluster { +public: + RedisCluster(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(pool_opts, connection_opts) {} + + // Construct RedisCluster with URI: + // "tcp://127.0.0.1" or "tcp://127.0.0.1:6379" + // Only need to specify one URI. + explicit RedisCluster(const std::string &uri); + + RedisCluster(const RedisCluster &) = delete; + RedisCluster& operator=(const RedisCluster &) = delete; + + RedisCluster(RedisCluster &&) = default; + RedisCluster& operator=(RedisCluster &&) = default; + + Redis redis(const StringView &hash_tag); + + Pipeline pipeline(const StringView &hash_tag); + + Transaction transaction(const StringView &hash_tag, bool piped = false); + + Subscriber subscriber(); + + template + auto command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // KEY commands. + + long long del(const StringView &key); + + template + long long del(Input first, Input last); + + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + OptionalString dump(const StringView &key); + + long long exists(const StringView &key); + + template + long long exists(Input first, Input last); + + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + bool expire(const StringView &key, long long timeout); + + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + bool expireat(const StringView &key, long long timestamp); + + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + bool persist(const StringView &key); + + bool pexpire(const StringView &key, long long timeout); + + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + bool pexpireat(const StringView &key, long long timestamp); + + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + long long pttl(const StringView &key); + + void rename(const StringView &key, const StringView &newkey); + + bool renamenx(const StringView &key, const StringView &newkey); + + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + long long touch(const StringView &key); + + template + long long touch(Input first, Input last); + + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + long long ttl(const StringView &key); + + std::string type(const StringView &key); + + long long unlink(const StringView &key); + + template + long long unlink(Input first, Input last); + + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + // STRING commands. + + long long append(const StringView &key, const StringView &str); + + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + long long decr(const StringView &key); + + long long decrby(const StringView &key, long long decrement); + + OptionalString get(const StringView &key); + + long long getbit(const StringView &key, long long offset); + + std::string getrange(const StringView &key, long long start, long long end); + + OptionalString getset(const StringView &key, const StringView &val); + + long long incr(const StringView &key); + + long long incrby(const StringView &key, long long increment); + + double incrbyfloat(const StringView &key, double increment); + + template + void mget(Input first, Input last, Output output); + + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + template + void mset(Input first, Input last); + + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + template + bool msetnx(Input first, Input last); + + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + void setex(const StringView &key, + long long ttl, + const StringView &val); + + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + bool setnx(const StringView &key, const StringView &val); + + long long setrange(const StringView &key, long long offset, const StringView &val); + + long long strlen(const StringView &key); + + // LIST commands. + + OptionalStringPair blpop(const StringView &key, long long timeout); + + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + OptionalStringPair brpop(const StringView &key, long long timeout); + + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + OptionalString lindex(const StringView &key, long long index); + + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + long long llen(const StringView &key); + + OptionalString lpop(const StringView &key); + + long long lpush(const StringView &key, const StringView &val); + + template + long long lpush(const StringView &key, Input first, Input last); + + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + long long lpushx(const StringView &key, const StringView &val); + + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + long long lrem(const StringView &key, long long count, const StringView &val); + + void lset(const StringView &key, long long index, const StringView &val); + + void ltrim(const StringView &key, long long start, long long stop); + + OptionalString rpop(const StringView &key); + + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + long long rpush(const StringView &key, const StringView &val); + + template + long long rpush(const StringView &key, Input first, Input last); + + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + long long hdel(const StringView &key, const StringView &field); + + template + long long hdel(const StringView &key, Input first, Input last); + + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + bool hexists(const StringView &key, const StringView &field); + + OptionalString hget(const StringView &key, const StringView &field); + + template + void hgetall(const StringView &key, Output output); + + long long hincrby(const StringView &key, const StringView &field, long long increment); + + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + template + void hkeys(const StringView &key, Output output); + + long long hlen(const StringView &key); + + template + void hmget(const StringView &key, Input first, Input last, Output output); + + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + template + void hmset(const StringView &key, Input first, Input last); + + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + bool hset(const StringView &key, const StringView &field, const StringView &val); + + bool hset(const StringView &key, const std::pair &item); + + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + bool hsetnx(const StringView &key, const std::pair &item); + + long long hstrlen(const StringView &key, const StringView &field); + + template + void hvals(const StringView &key, Output output); + + // SET commands. + + long long sadd(const StringView &key, const StringView &member); + + template + long long sadd(const StringView &key, Input first, Input last); + + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + long long scard(const StringView &key); + + template + void sdiff(Input first, Input last, Output output); + + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + long long sdiffstore(const StringView &destination, const StringView &key); + + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + void sinter(Input first, Input last, Output output); + + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + long long sinterstore(const StringView &destination, const StringView &key); + + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + bool sismember(const StringView &key, const StringView &member); + + template + void smembers(const StringView &key, Output output); + + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + OptionalString spop(const StringView &key); + + template + void spop(const StringView &key, long long count, Output output); + + OptionalString srandmember(const StringView &key); + + template + void srandmember(const StringView &key, long long count, Output output); + + long long srem(const StringView &key, const StringView &member); + + template + long long srem(const StringView &key, Input first, Input last); + + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + template + void sunion(Input first, Input last, Output output); + + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + long long sunionstore(const StringView &destination, const StringView &key); + + template + long long sunionstore(const StringView &destination, Input first, Input last); + + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + long long zcard(const StringView &key); + + template + long long zcount(const StringView &key, const Interval &interval); + + double zincrby(const StringView &key, double increment, const StringView &member); + + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + long long zlexcount(const StringView &key, const Interval &interval); + + Optional> zpopmax(const StringView &key); + + template + void zpopmax(const StringView &key, long long count, Output output); + + Optional> zpopmin(const StringView &key); + + template + void zpopmin(const StringView &key, long long count, Output output); + + // If *output* is an iterator of a container of string, + // we send *ZRANGE key start stop* command. + // If it's an iterator of a container of pair, + // we send *ZRANGE key start stop WITHSCORES* command. + // + // The following code sends *ZRANGE* without the *WITHSCORES* option: + // + // vector result; + // redis.zrange("key", 0, -1, back_inserter(result)); + // + // On the other hand, the following code sends command with *WITHSCORES* option: + // + // unordered_map with_score; + // redis.zrange("key", 0, -1, inserter(with_score, with_score.end())); + // + // This also applies to other commands with the *WITHSCORES* option, + // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrank(const StringView &key, const StringView &member); + + long long zrem(const StringView &key, const StringView &member); + + template + long long zrem(const StringView &key, Input first, Input last); + + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + OptionalDouble zscore(const StringView &key, const StringView &member); + + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + bool pfadd(const StringView &key, const StringView &element); + + template + bool pfadd(const StringView &key, Input first, Input last); + + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first, last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + class Command { + public: + explicit Command(const StringView &cmd_name) : _cmd_name(cmd_name) {} + + template + void operator()(Connection &connection, Args &&...args) const { + CmdArgs cmd_args; + cmd_args.append(_cmd_name, std::forward(args)...); + connection.send(cmd_args); + } + + private: + StringView _cmd_name; + }; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + ReplyUPtr _command(Cmd cmd, Connection &connection, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::true_type, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::false_type, Input &&first, Args &&...args); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _range_command(Cmd cmd, std::true_type, Input input, Args &&...args); + + template + ReplyUPtr _range_command(Cmd cmd, std::false_type, Input input, Args &&...args); + + void _asking(Connection &connection); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + ShardsPool _pool; +}; + +} + +} + +#include "redis_cluster.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.hpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.hpp new file mode 100644 index 000000000..61da3f062 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.hpp @@ -0,0 +1,1415 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP + +#include +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" +#include "shards_pool.h" + +namespace sw { + +namespace redis { + +template +auto RedisCluster::command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + return _command(cmd, + std::is_convertible::type, StringView>(), + std::forward(key), + std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type { + auto cmd = Command(cmd_name); + + return _generic_command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type { + auto r = command(cmd_name, std::forward(key), std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(key), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (first == last || std::next(first) == last) { + throw Error("command: invalid range"); + } + + const auto &key = *first; + ++first; + + auto cmd = [&key](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + cmd_args.append(key); + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long RedisCluster::del(Input first, Input last) { + if (first == last) { + throw Error("DEL: no key specified"); + } + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::exists(Input first, Input last) { + if (first == last) { + throw Error("EXISTS: no key specified"); + } + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool RedisCluster::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool RedisCluster::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +inline bool RedisCluster::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool RedisCluster::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void RedisCluster::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long RedisCluster::touch(Input first, Input last) { + if (first == last) { + throw Error("TOUCH: no key specified"); + } + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::unlink(Input first, Input last) { + if (first == last) { + throw Error("UNLINK: no key specified"); + } + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +// STRING commands. + +template +long long RedisCluster::bitop(BitOp op, const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("BITOP: no key specified"); + } + + auto reply = _command(cmd::bitop_range, destination, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::mget(Input first, Input last, Output output) { + if (first == last) { + throw Error("MGET: no key specified"); + } + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::mset(Input first, Input last) { + if (first == last) { + throw Error("MSET: no key specified"); + } + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool RedisCluster::msetnx(Input first, Input last) { + if (first == last) { + throw Error("MSETNX: no key specified"); + } + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void RedisCluster::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void RedisCluster::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair RedisCluster::blpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BLPOP: no key specified"); + } + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BRPOP: no key specified"); + } + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString RedisCluster::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long RedisCluster::lpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("LPUSH: no key specified"); + } + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long RedisCluster::rpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("RPUSH: no key specified"); + } + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long RedisCluster::hdel(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HDEL: no key specified"); + } + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmget(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("HMGET: no key specified"); + } + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmset(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HMSET: no key specified"); + } + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +inline void RedisCluster::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long RedisCluster::sadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SADD: no key specified"); + } + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sdiff(Input first, Input last, Output output) { + if (first == last) { + throw Error("SDIFF: no key specified"); + } + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sdiffstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SDIFFSTORE: no key specified"); + } + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sinter(Input first, Input last, Output output) { + if (first == last) { + throw Error("SINTER: no key specified"); + } + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sinterstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SINTERSTORE: no key specified"); + } + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::srem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SREM: no key specified"); + } + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void RedisCluster::sunion(Input first, Input last, Output output) { + if (first == last) { + throw Error("SUNION: no key specified"); + } + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sunionstore(const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("SUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto RedisCluster::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto RedisCluster::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long RedisCluster::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + if (first == last) { + throw Error("ZADD: no key specified"); + } + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZINTERSTORE: no key specified"); + } + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zrem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("ZREM: no key specified"); + } + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long RedisCluster::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool RedisCluster::pfadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("PFADD: no key specified"); + } + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::pfcount(Input first, Input last) { + if (first == last) { + throw Error("PFCOUNT: no key specified"); + } + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::pfmerge(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("PFMERGE: no key specified"); + } + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long RedisCluster::geoadd(const StringView &key, + Input first, + Input last) { + if (first == last) { + throw Error("GEOADD: no key specified"); + } + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::geohash(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOHASH: no key specified"); + } + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::geopos(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOPOS: no key specified"); + } + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, *keys.begin(), script, keys, args); + + return reply::parse(*reply); +} + +template +void RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, *keys.begin(), script, keys, args); + + reply::to_array(*reply, output); +} + +template +Result RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::evalsha, *keys.begin(), script, keys, args); + + return reply::parse(*reply); +} + +template +void RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = command(cmd::evalsha, *keys.begin(), script, keys, args); + + reply::to_array(*reply, output); +} + +// Stream commands. + +template +long long RedisCluster::xack(const StringView &key, + const StringView &group, + Input first, + Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto RedisCluster::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup, key, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = _command(cmd::xreadgroup_range, + first->first, + group, + consumer, + first, + last, + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup_block, + key, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = _command(cmd::xreadgroup_block_range, + first->first, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type { + return command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type { + auto k = std::to_string(std::forward(key)); + return command(cmd, k, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::true_type, const StringView &key, Args &&...args) { + return _command(cmd, key, key, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::false_type, Input &&first, Args &&...args) { + return _range_command(cmd, + std::is_convertible< + typename std::decay< + decltype(*std::declval())>::type, StringView>(), + std::forward(first), + std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::true_type, Input input, Args &&...args) { + return _command(cmd, *input, input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::false_type, Input input, Args &&...args) { + return _command(cmd, std::get<0>(*input), input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, Connection &connection, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + return connection.recv(); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, const StringView &key, Args &&...args) { + for (auto idx = 0; idx < 2; ++idx) { + try { + auto guarded_connection = _pool.fetch(key); + + return _command(cmd, guarded_connection.connection(), std::forward(args)...); + } catch (const IoError &err) { + // When master is down, one of its replicas will be promoted to be the new master. + // If we try to send command to the old master, we'll get an *IoError*. + // In this case, we need to update the slots mapping. + _pool.update(); + } catch (const ClosedError &err) { + // Node might be removed. + // 1. Get up-to-date slot mapping to check if the node still exists. + _pool.update(); + + // TODO: + // 2. If it's NOT exist, update slot mapping, and retry. + // 3. If it's still exist, that means the node is down, NOT removed, throw exception. + } catch (const MovedError &err) { + // Slot mapping has been changed, update it and try again. + _pool.update(); + } catch (const AskError &err) { + auto guarded_connection = _pool.fetch(err.node()); + auto &connection = guarded_connection.connection(); + + // 1. send ASKING command. + _asking(connection); + + // 2. resend last command. + try { + return _command(cmd, connection, std::forward(args)...); + } catch (const MovedError &err) { + throw Error("Slot migrating... ASKING node hasn't been set to IMPORTING state"); + } + } // For other exceptions, just throw it. + } + + // Possible failures: + // 1. Source node has already run 'CLUSTER SETSLOT xxx NODE xxx', + // while the destination node has NOT run it. + // In this case, client will be redirected by both nodes with MovedError. + // 2. Other failures... + throw Error("Failed to send command with key: " + std::string(key.data(), key.size())); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr RedisCluster::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.cpp new file mode 100644 index 000000000..dc70d30cc --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.cpp @@ -0,0 +1,150 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "reply.h" + +namespace sw { + +namespace redis { + +namespace reply { + +std::string to_status(redisReply &reply) { + if (!reply::is_status(reply)) { + throw ProtoError("Expect STATUS reply"); + } + + if (reply.str == nullptr) { + throw ProtoError("A null status reply"); + } + + // Old version hiredis' *redisReply::len* is of type int. + // So we CANNOT have something like: *return {reply.str, reply.len}*. + return std::string(reply.str, reply.len); +} + +std::string parse(ParseTag, redisReply &reply) { + if (!reply::is_string(reply) && !reply::is_status(reply)) { + throw ProtoError("Expect STRING reply"); + } + + if (reply.str == nullptr) { + throw ProtoError("A null string reply"); + } + + // Old version hiredis' *redisReply::len* is of type int. + // So we CANNOT have something like: *return {reply.str, reply.len}*. + return std::string(reply.str, reply.len); +} + +long long parse(ParseTag, redisReply &reply) { + if (!reply::is_integer(reply)) { + throw ProtoError("Expect INTEGER reply"); + } + + return reply.integer; +} + +double parse(ParseTag, redisReply &reply) { + return std::stod(parse(reply)); +} + +bool parse(ParseTag, redisReply &reply) { + auto ret = parse(reply); + + if (ret == 1) { + return true; + } else if (ret == 0) { + return false; + } else { + throw ProtoError("Invalid bool reply: " + std::to_string(ret)); + } +} + +void parse(ParseTag, redisReply &reply) { + if (!reply::is_status(reply)) { + throw ProtoError("Expect STATUS reply"); + } + + if (reply.str == nullptr) { + throw ProtoError("A null status reply"); + } + + static const std::string OK = "OK"; + + // Old version hiredis' *redisReply::len* is of type int. + // So we have to cast it to an unsigned int. + if (static_cast(reply.len) != OK.size() + || OK.compare(0, OK.size(), reply.str, reply.len) != 0) { + throw ProtoError("NOT ok status reply: " + reply::to_status(reply)); + } +} + +void rewrite_set_reply(redisReply &reply) { + if (is_nil(reply)) { + // Failed to set, and make it a FALSE reply. + reply.type = REDIS_REPLY_INTEGER; + reply.integer = 0; + + return; + } + + // Check if it's a "OK" status reply. + reply::parse(reply); + + assert(is_status(reply) && reply.str != nullptr); + + free(reply.str); + + // Make it a TRUE reply. + reply.type = REDIS_REPLY_INTEGER; + reply.integer = 1; +} + +void rewrite_georadius_reply(redisReply &reply) { + if (is_array(reply) && reply.element == nullptr) { + // Make it a nil reply. + reply.type = REDIS_REPLY_NIL; + } +} + +namespace detail { + +bool is_flat_array(redisReply &reply) { + assert(reply::is_array(reply)); + + // Empty array reply. + if (reply.element == nullptr || reply.elements == 0) { + return false; + } + + auto *sub_reply = reply.element[0]; + + // Null element. + if (sub_reply == nullptr) { + return false; + } + + return !reply::is_array(*sub_reply); +} + +} + +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.h new file mode 100644 index 000000000..b309de5bb --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.h @@ -0,0 +1,363 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REPLY_H +#define SEWENEW_REDISPLUSPLUS_REPLY_H + +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +struct ReplyDeleter { + void operator()(redisReply *reply) const { + if (reply != nullptr) { + freeReplyObject(reply); + } + } +}; + +using ReplyUPtr = std::unique_ptr; + +namespace reply { + +template +struct ParseTag {}; + +template +inline T parse(redisReply &reply) { + return parse(ParseTag(), reply); +} + +void parse(ParseTag, redisReply &reply); + +std::string parse(ParseTag, redisReply &reply); + +long long parse(ParseTag, redisReply &reply); + +double parse(ParseTag, redisReply &reply); + +bool parse(ParseTag, redisReply &reply); + +template +Optional parse(ParseTag>, redisReply &reply); + +template +std::pair parse(ParseTag>, redisReply &reply); + +template +std::tuple parse(ParseTag>, redisReply &reply); + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template +long long parse_scan_reply(redisReply &reply, Output output); + +inline bool is_error(redisReply &reply) { + return reply.type == REDIS_REPLY_ERROR; +} + +inline bool is_nil(redisReply &reply) { + return reply.type == REDIS_REPLY_NIL; +} + +inline bool is_string(redisReply &reply) { + return reply.type == REDIS_REPLY_STRING; +} + +inline bool is_status(redisReply &reply) { + return reply.type == REDIS_REPLY_STATUS; +} + +inline bool is_integer(redisReply &reply) { + return reply.type == REDIS_REPLY_INTEGER; +} + +inline bool is_array(redisReply &reply) { + return reply.type == REDIS_REPLY_ARRAY; +} + +std::string to_status(redisReply &reply); + +template +void to_array(redisReply &reply, Output output); + +// Rewrite set reply to bool type +void rewrite_set_reply(redisReply &reply); + +// Rewrite georadius reply to OptionalLongLong type +void rewrite_georadius_reply(redisReply &reply); + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple; + +} + +// Inline implementations. + +namespace reply { + +namespace detail { + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.element == nullptr) { + // Empty array. + return; + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + auto *sub_reply = reply.element[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null array element reply"); + } + + *output = parse::type>(*sub_reply); + + ++output; + } +} + +bool is_flat_array(redisReply &reply); + +template +void to_flat_array(redisReply &reply, Output output) { + if (reply.element == nullptr) { + // Empty array. + return; + } + + if (reply.elements % 2 != 0) { + throw ProtoError("Not string pair array reply"); + } + + for (std::size_t idx = 0; idx != reply.elements; idx += 2) { + auto *key_reply = reply.element[idx]; + auto *val_reply = reply.element[idx + 1]; + if (key_reply == nullptr || val_reply == nullptr) { + throw ProtoError("Null string array reply"); + } + + using Pair = typename IterType::type; + using FirstType = typename std::decay::type; + using SecondType = typename std::decay::type; + *output = std::make_pair(parse(*key_reply), + parse(*val_reply)); + + ++output; + } +} + +template +void to_array(std::true_type, redisReply &reply, Output output) { + if (is_flat_array(reply)) { + to_flat_array(reply, output); + } else { + to_array(reply, output); + } +} + +template +void to_array(std::false_type, redisReply &reply, Output output) { + to_array(reply, output); +} + +template +std::tuple parse_tuple(redisReply **reply, std::size_t idx) { + assert(reply != nullptr); + + auto *sub_reply = reply[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null reply"); + } + + return std::make_tuple(parse(*sub_reply)); +} + +template +auto parse_tuple(redisReply **reply, std::size_t idx) -> + typename std::enable_if>::type { + assert(reply != nullptr); + + return std::tuple_cat(parse_tuple(reply, idx), + parse_tuple(reply, idx + 1)); +} + +} + +template +Optional parse(ParseTag>, redisReply &reply) { + if (reply::is_nil(reply)) { + return {}; + } + + return Optional(parse(reply)); +} + +template +std::pair parse(ParseTag>, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != 2) { + throw ProtoError("NOT key-value PAIR reply"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null PAIR reply"); + } + + auto *first = reply.element[0]; + auto *second = reply.element[1]; + if (first == nullptr || second == nullptr) { + throw ProtoError("Null pair reply"); + } + + return std::make_pair(parse::type>(*first), + parse::type>(*second)); +} + +template +std::tuple parse(ParseTag>, redisReply &reply) { + constexpr auto size = sizeof...(Args); + + static_assert(size > 0, "DO NOT support parsing tuple with 0 element"); + + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != size) { + throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null TUPLE reply"); + } + + return detail::parse_tuple(reply.element, 0); +} + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::back_inserter(container)); + + return container; +} + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::inserter(container, container.end())); + + return container; +} + +template +long long parse_scan_reply(redisReply &reply, Output output) { + if (reply.elements != 2 || reply.element == nullptr) { + throw ProtoError("Invalid scan reply"); + } + + auto *cursor_reply = reply.element[0]; + auto *data_reply = reply.element[1]; + if (cursor_reply == nullptr || data_reply == nullptr) { + throw ProtoError("Invalid cursor reply or data reply"); + } + + auto cursor_str = reply::parse(*cursor_reply); + auto new_cursor = 0; + try { + new_cursor = std::stoll(cursor_str); + } catch (const std::exception &e) { + throw ProtoError("Invalid cursor reply: " + cursor_str); + } + + reply::to_array(*data_reply, output); + + return new_cursor; +} + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + detail::to_array(typename IsKvPairIter::type(), reply, output); +} + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple { + if (!is_array(reply) || reply.elements != 4) { + throw ProtoError("expect array reply with 4 elements"); + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + if (reply.element[idx] == nullptr) { + throw ProtoError("null array reply"); + } + } + + auto num = parse(*(reply.element[0])); + auto start = parse(*(reply.element[1])); + auto end = parse(*(reply.element[2])); + + auto &entry_reply = *(reply.element[3]); + if (!is_nil(entry_reply)) { + to_array(entry_reply, output); + } + + return std::make_tuple(num, std::move(start), std::move(end)); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REPLY_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.cpp new file mode 100644 index 000000000..a7d88a181 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.cpp @@ -0,0 +1,361 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "sentinel.h" +#include +#include +#include +#include +#include "redis.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +class Sentinel::Iterator { +public: + Iterator(std::list &healthy_sentinels, + std::list &broken_sentinels) : + _healthy_sentinels(healthy_sentinels), + _broken_sentinels(broken_sentinels) { + reset(); + } + + Connection& next(); + + void reset(); + +private: + std::list &_healthy_sentinels; + + std::size_t _healthy_size = 0; + + std::list &_broken_sentinels; + + std::size_t _broken_size = 0; +}; + +Connection& Sentinel::Iterator::next() { + while (_healthy_size > 0) { + assert(_healthy_sentinels.size() >= _healthy_size); + + --_healthy_size; + + auto &connection = _healthy_sentinels.front(); + if (connection.broken()) { + _broken_sentinels.push_front(connection.options()); + ++_broken_size; + + _healthy_sentinels.pop_front(); + } else { + _healthy_sentinels.splice(_healthy_sentinels.end(), + _healthy_sentinels, + _healthy_sentinels.begin()); + + return _healthy_sentinels.back(); + } + } + + while (_broken_size > 0) { + assert(_broken_sentinels.size() >= _broken_size); + + --_broken_size; + + try { + const auto &opt = _broken_sentinels.front(); + Connection connection(opt); + _healthy_sentinels.push_back(std::move(connection)); + + _broken_sentinels.pop_front(); + + return _healthy_sentinels.back(); + } catch (const Error &e) { + // Failed to connect to sentinel. + _broken_sentinels.splice(_broken_sentinels.end(), + _broken_sentinels, + _broken_sentinels.begin()); + } + } + + throw StopIterError(); +} + +void Sentinel::Iterator::reset() { + _healthy_size = _healthy_sentinels.size(); + _broken_size = _broken_sentinels.size(); +} + +Sentinel::Sentinel(const SentinelOptions &sentinel_opts) : + _broken_sentinels(_parse_options(sentinel_opts)), + _sentinel_opts(sentinel_opts) { + if (_sentinel_opts.connect_timeout == std::chrono::milliseconds(0) + || _sentinel_opts.socket_timeout == std::chrono::milliseconds(0)) { + throw Error("With sentinel, connection timeout and socket timeout cannot be 0"); + } +} + +Connection Sentinel::master(const std::string &master_name, const ConnectionOptions &opts) { + std::lock_guard lock(_mutex); + + Iterator iter(_healthy_sentinels, _broken_sentinels); + std::size_t retries = 0; + while (true) { + try { + auto &sentinel = iter.next(); + + auto master = _get_master_addr_by_name(sentinel, master_name); + if (!master) { + // Try the next sentinel. + continue; + } + + auto connection = _connect_redis(*master, opts); + if (_get_role(connection) != Role::MASTER) { + // Retry the whole process at most SentinelOptions::max_retry times. + ++retries; + if (retries > _sentinel_opts.max_retry) { + throw Error("Failed to get master from sentinel"); + } + + std::this_thread::sleep_for(_sentinel_opts.retry_interval); + + // Restart the iteration. + iter.reset(); + continue; + } + + return connection; + } catch (const StopIterError &e) { + throw; + } catch (const Error &e) { + continue; + } + } +} + +Connection Sentinel::slave(const std::string &master_name, const ConnectionOptions &opts) { + std::lock_guard lock(_mutex); + + Iterator iter(_healthy_sentinels, _broken_sentinels); + std::size_t retries = 0; + while (true) { + try { + auto &sentinel = iter.next(); + + auto slaves = _get_slave_addr_by_name(sentinel, master_name); + if (slaves.empty()) { + // Try the next sentinel. + continue; + } + + // Normally slaves list is NOT very large, so there won't be a performance problem. + auto slave_iter = std::find(slaves.begin(), + slaves.end(), + Node{opts.host, opts.port}); + if (slave_iter != slaves.end() && slave_iter != slaves.begin()) { + // The given node is still a valid slave. Try it first. + std::swap(*(slaves.begin()), *slave_iter); + } + + for (const auto &slave : slaves) { + try { + auto connection = _connect_redis(slave, opts); + if (_get_role(connection) != Role::SLAVE) { + // Retry the whole process at most SentinelOptions::max_retry times. + ++retries; + if (retries > _sentinel_opts.max_retry) { + throw Error("Failed to get slave from sentinel"); + } + + std::this_thread::sleep_for(_sentinel_opts.retry_interval); + + // Restart the iteration. + iter.reset(); + break; + } + + return connection; + } catch (const Error &e) { + // Try the next slave. + continue; + } + } + } catch (const StopIterError &e) { + throw; + } catch (const Error &e) { + continue; + } + } +} + +Optional Sentinel::_get_master_addr_by_name(Connection &connection, const StringView &name) { + connection.send("SENTINEL GET-MASTER-ADDR-BY-NAME %b", name.data(), name.size()); + + auto reply = connection.recv(); + + assert(reply); + + auto master = reply::parse>>(*reply); + if (!master) { + return {}; + } + + int port = 0; + try { + port = std::stoi(master->second); + } catch (const std::exception &) { + throw ProtoError("Master port is invalid: " + master->second); + } + + return Optional{Node{master->first, port}}; +} + +std::vector Sentinel::_get_slave_addr_by_name(Connection &connection, + const StringView &name) { + try { + connection.send("SENTINEL SLAVES %b", name.data(), name.size()); + + auto reply = connection.recv(); + + assert(reply); + + auto slaves = _parse_slave_info(*reply); + + // Make slave list random. + std::mt19937 gen(std::random_device{}()); + std::shuffle(slaves.begin(), slaves.end(), gen); + + return slaves; + } catch (const ReplyError &e) { + // Unknown master name. + return {}; + } +} + +std::vector Sentinel::_parse_slave_info(redisReply &reply) const { + using SlaveInfo = std::unordered_map; + + auto slaves = reply::parse>(reply); + + std::vector nodes; + for (const auto &slave : slaves) { + auto flags_iter = slave.find("flags"); + auto ip_iter = slave.find("ip"); + auto port_iter = slave.find("port"); + if (flags_iter == slave.end() || ip_iter == slave.end() || port_iter == slave.end()) { + throw ProtoError("Invalid slave info"); + } + + // This slave is down, e.g. 's_down,slave,disconnected' + if (flags_iter->second != "slave") { + continue; + } + + int port = 0; + try { + port = std::stoi(port_iter->second); + } catch (const std::exception &) { + throw ProtoError("Slave port is invalid: " + port_iter->second); + } + + nodes.push_back(Node{ip_iter->second, port}); + } + + return nodes; +} + +Connection Sentinel::_connect_redis(const Node &node, ConnectionOptions opts) { + opts.host = node.host; + opts.port = node.port; + + return Connection(opts); +} + +Role Sentinel::_get_role(Connection &connection) { + connection.send("INFO REPLICATION"); + auto reply = connection.recv(); + + assert(reply); + auto info = reply::parse(*reply); + + auto start = info.find("role:"); + if (start == std::string::npos) { + throw ProtoError("Invalid INFO REPLICATION reply"); + } + start += 5; + auto stop = info.find("\r\n", start); + if (stop == std::string::npos) { + throw ProtoError("Invalid INFO REPLICATION reply"); + } + + auto role = info.substr(start, stop - start); + if (role == "master") { + return Role::MASTER; + } else if (role == "slave") { + return Role::SLAVE; + } else { + throw Error("Invalid role: " + role); + } +} + +std::list Sentinel::_parse_options(const SentinelOptions &opts) const { + std::list options; + for (const auto &node : opts.nodes) { + ConnectionOptions opt; + opt.host = node.first; + opt.port = node.second; + opt.password = opts.password; + opt.keep_alive = opts.keep_alive; + opt.connect_timeout = opts.connect_timeout; + opt.socket_timeout = opts.socket_timeout; + + options.push_back(opt); + } + + return options; +} + +SimpleSentinel::SimpleSentinel(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role) : + _sentinel(sentinel), + _master_name(master_name), + _role(role) { + if (!_sentinel) { + throw Error("Sentinel cannot be null"); + } + + if (_role != Role::MASTER && _role != Role::SLAVE) { + throw Error("Role must be Role::MASTER or Role::SLAVE"); + } +} + +Connection SimpleSentinel::create(const ConnectionOptions &opts) { + assert(_sentinel); + + if (_role == Role::MASTER) { + return _sentinel->master(_master_name, opts); + } + + assert(_role == Role::SLAVE); + + return _sentinel->slave(_master_name, opts); +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.h new file mode 100644 index 000000000..e80d1e56a --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.h @@ -0,0 +1,138 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SENTINEL_H +#define SEWENEW_REDISPLUSPLUS_SENTINEL_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "shards.h" +#include "reply.h" + +namespace sw { + +namespace redis { + +struct SentinelOptions { + std::vector> nodes; + + std::string password; + + bool keep_alive = true; + + std::chrono::milliseconds connect_timeout{100}; + + std::chrono::milliseconds socket_timeout{100}; + + std::chrono::milliseconds retry_interval{100}; + + std::size_t max_retry = 2; +}; + +class Sentinel { +public: + explicit Sentinel(const SentinelOptions &sentinel_opts); + + Sentinel(const Sentinel &) = delete; + Sentinel& operator=(const Sentinel &) = delete; + + Sentinel(Sentinel &&) = delete; + Sentinel& operator=(Sentinel &&) = delete; + + ~Sentinel() = default; + +private: + Connection master(const std::string &master_name, const ConnectionOptions &opts); + + Connection slave(const std::string &master_name, const ConnectionOptions &opts); + + class Iterator; + + friend class SimpleSentinel; + + std::list _parse_options(const SentinelOptions &opts) const; + + Optional _get_master_addr_by_name(Connection &connection, const StringView &name); + + std::vector _get_slave_addr_by_name(Connection &connection, const StringView &name); + + Connection _connect_redis(const Node &node, ConnectionOptions opts); + + Role _get_role(Connection &connection); + + std::vector _parse_slave_info(redisReply &reply) const; + + std::list _healthy_sentinels; + + std::list _broken_sentinels; + + SentinelOptions _sentinel_opts; + + std::mutex _mutex; +}; + +class SimpleSentinel { +public: + SimpleSentinel(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role); + + SimpleSentinel() = default; + + SimpleSentinel(const SimpleSentinel &) = default; + SimpleSentinel& operator=(const SimpleSentinel &) = default; + + SimpleSentinel(SimpleSentinel &&) = default; + SimpleSentinel& operator=(SimpleSentinel &&) = default; + + ~SimpleSentinel() = default; + + explicit operator bool() const { + return bool(_sentinel); + } + + Connection create(const ConnectionOptions &opts); + +private: + std::shared_ptr _sentinel; + + std::string _master_name; + + Role _role = Role::MASTER; +}; + +class StopIterError : public Error { +public: + StopIterError() : Error("StopIterError") {} + + StopIterError(const StopIterError &) = default; + StopIterError& operator=(const StopIterError &) = default; + + StopIterError(StopIterError &&) = default; + StopIterError& operator=(StopIterError &&) = default; + + virtual ~StopIterError() = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SENTINEL_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.cpp new file mode 100644 index 000000000..e06d2a732 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.cpp @@ -0,0 +1,50 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "shards.h" + +namespace sw { + +namespace redis { + +RedirectionError::RedirectionError(const std::string &msg): ReplyError(msg) { + std::tie(_slot, _node) = _parse_error(msg); +} + +std::pair RedirectionError::_parse_error(const std::string &msg) const { + // "slot ip:port" + auto space_pos = msg.find(" "); + auto colon_pos = msg.find(":"); + if (space_pos == std::string::npos + || colon_pos == std::string::npos + || colon_pos < space_pos) { + throw ProtoError("Invalid ASK error message: " + msg); + } + + try { + auto slot = std::stoull(msg.substr(0, space_pos)); + auto host = msg.substr(space_pos + 1, colon_pos - space_pos - 1); + auto port = std::stoi(msg.substr(colon_pos + 1)); + + return {slot, {host, port}}; + } catch (const std::exception &e) { + throw ProtoError("Invalid ASK error message: " + msg); + } +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.h new file mode 100644 index 000000000..a0593acbc --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.h @@ -0,0 +1,115 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_H + +#include +#include +#include "errors.h" + +namespace sw { + +namespace redis { + +using Slot = std::size_t; + +struct SlotRange { + Slot min; + Slot max; +}; + +inline bool operator<(const SlotRange &lhs, const SlotRange &rhs) { + return lhs.max < rhs.max; +} + +struct Node { + std::string host; + int port; +}; + +inline bool operator==(const Node &lhs, const Node &rhs) { + return lhs.host == rhs.host && lhs.port == rhs.port; +} + +struct NodeHash { + std::size_t operator()(const Node &node) const noexcept { + auto host_hash = std::hash{}(node.host); + auto port_hash = std::hash{}(node.port); + return host_hash ^ (port_hash << 1); + } +}; + +using Shards = std::map; + +class RedirectionError : public ReplyError { +public: + RedirectionError(const std::string &msg); + + RedirectionError(const RedirectionError &) = default; + RedirectionError& operator=(const RedirectionError &) = default; + + RedirectionError(RedirectionError &&) = default; + RedirectionError& operator=(RedirectionError &&) = default; + + virtual ~RedirectionError() = default; + + Slot slot() const { + return _slot; + } + + const Node& node() const { + return _node; + } + +private: + std::pair _parse_error(const std::string &msg) const; + + Slot _slot = 0; + Node _node; +}; + +class MovedError : public RedirectionError { +public: + explicit MovedError(const std::string &msg) : RedirectionError(msg) {} + + MovedError(const MovedError &) = default; + MovedError& operator=(const MovedError &) = default; + + MovedError(MovedError &&) = default; + MovedError& operator=(MovedError &&) = default; + + virtual ~MovedError() = default; +}; + +class AskError : public RedirectionError { +public: + explicit AskError(const std::string &msg) : RedirectionError(msg) {} + + AskError(const AskError &) = default; + AskError& operator=(const AskError &) = default; + + AskError(AskError &&) = default; + AskError& operator=(AskError &&) = default; + + virtual ~AskError() = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.cpp new file mode 100644 index 000000000..436cc265c --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.cpp @@ -0,0 +1,319 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "shards_pool.h" +#include +#include "errors.h" + +namespace sw { + +namespace redis { + +const std::size_t ShardsPool::SHARDS; + +ShardsPool::ShardsPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts) : + _pool_opts(pool_opts), + _connection_opts(connection_opts) { + if (_connection_opts.type != ConnectionType::TCP) { + throw Error("Only support TCP connection for Redis Cluster"); + } + + Connection connection(_connection_opts); + + _shards = _cluster_slots(connection); + + _init_pool(_shards); +} + +ShardsPool::ShardsPool(ShardsPool &&that) { + std::lock_guard lock(that._mutex); + + _move(std::move(that)); +} + +ShardsPool& ShardsPool::operator=(ShardsPool &&that) { + if (this != &that) { + std::lock(_mutex, that._mutex); + std::lock_guard lock_this(_mutex, std::adopt_lock); + std::lock_guard lock_that(that._mutex, std::adopt_lock); + + _move(std::move(that)); + } + + return *this; +} + +GuardedConnection ShardsPool::fetch(const StringView &key) { + auto slot = _slot(key); + + return _fetch(slot); +} + +GuardedConnection ShardsPool::fetch() { + auto slot = _slot(); + + return _fetch(slot); +} + +GuardedConnection ShardsPool::fetch(const Node &node) { + std::lock_guard lock(_mutex); + + auto iter = _pools.find(node); + if (iter == _pools.end()) { + // Node doesn't exist, and it should be a newly created node. + // So add a new connection pool. + iter = _add_node(node); + } + + assert(iter != _pools.end()); + + return GuardedConnection(iter->second); +} + +void ShardsPool::update() { + // My might send command to a removed node. + // Try at most 3 times. + for (auto idx = 0; idx < 3; ++idx) { + try { + // Randomly pick a connection. + auto guarded_connection = fetch(); + auto shards = _cluster_slots(guarded_connection.connection()); + + std::unordered_set nodes; + for (const auto &shard : shards) { + nodes.insert(shard.second); + } + + std::lock_guard lock(_mutex); + + // TODO: If shards is unchanged, no need to update, and return immediately. + + _shards = std::move(shards); + + // Remove non-existent nodes. + for (auto iter = _pools.begin(); iter != _pools.end(); ) { + if (nodes.find(iter->first) == nodes.end()) { + // Node has been removed. + _pools.erase(iter++); + } else { + ++iter; + } + } + + // Add connection pool for new nodes. + // In fact, connections will be created lazily. + for (const auto &node : nodes) { + if (_pools.find(node) == _pools.end()) { + _add_node(node); + } + } + + // Update successfully. + return; + } catch (const Error &) { + // continue; + } + } + + throw Error("Failed to update shards info"); +} + +ConnectionOptions ShardsPool::connection_options(const StringView &key) { + auto slot = _slot(key); + + return _connection_options(slot); +} + +ConnectionOptions ShardsPool::connection_options() { + auto slot = _slot(); + + return _connection_options(slot); +} +void ShardsPool::_move(ShardsPool &&that) { + _pool_opts = that._pool_opts; + _connection_opts = that._connection_opts; + _shards = std::move(that._shards); + _pools = std::move(that._pools); +} + +void ShardsPool::_init_pool(const Shards &shards) { + for (const auto &shard : shards) { + _add_node(shard.second); + } +} + +Shards ShardsPool::_cluster_slots(Connection &connection) const { + auto reply = _cluster_slots_command(connection); + + assert(reply); + + return _parse_reply(*reply); +} + +ReplyUPtr ShardsPool::_cluster_slots_command(Connection &connection) const { + connection.send("CLUSTER SLOTS"); + + return connection.recv(); +} + +Shards ShardsPool::_parse_reply(redisReply &reply) const { + if (!reply::is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.element == nullptr || reply.elements == 0) { + throw Error("Empty slots"); + } + + Shards shards; + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + auto *sub_reply = reply.element[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null slot info"); + } + + shards.emplace(_parse_slot_info(*sub_reply)); + } + + return shards; +} + +std::pair ShardsPool::_parse_slot_info(redisReply &reply) const { + if (reply.elements < 3 || reply.element == nullptr) { + throw ProtoError("Invalid slot info"); + } + + // Min slot id + auto *min_slot_reply = reply.element[0]; + if (min_slot_reply == nullptr) { + throw ProtoError("Invalid min slot"); + } + std::size_t min_slot = reply::parse(*min_slot_reply); + + // Max slot id + auto *max_slot_reply = reply.element[1]; + if (max_slot_reply == nullptr) { + throw ProtoError("Invalid max slot"); + } + std::size_t max_slot = reply::parse(*max_slot_reply); + + if (min_slot > max_slot) { + throw ProtoError("Invalid slot range"); + } + + // Master node info + auto *node_reply = reply.element[2]; + if (node_reply == nullptr + || !reply::is_array(*node_reply) + || node_reply->element == nullptr + || node_reply->elements < 2) { + throw ProtoError("Invalid node info"); + } + + auto master_host = reply::parse(*(node_reply->element[0])); + int master_port = reply::parse(*(node_reply->element[1])); + + // By now, we ignore node id and other replicas' info. + + return {SlotRange{min_slot, max_slot}, Node{master_host, master_port}}; +} + +Slot ShardsPool::_slot(const StringView &key) const { + // The following code is copied from: https://redis.io/topics/cluster-spec + // And I did some minor changes. + + const auto *k = key.data(); + auto keylen = key.size(); + + // start-end indexes of { and }. + std::size_t s = 0; + std::size_t e = 0; + + // Search the first occurrence of '{'. + for (s = 0; s < keylen; s++) + if (k[s] == '{') break; + + // No '{' ? Hash the whole key. This is the base case. + if (s == keylen) return crc16(k, keylen) & SHARDS; + + // '{' found? Check if we have the corresponding '}'. + for (e = s + 1; e < keylen; e++) + if (k[e] == '}') break; + + // No '}' or nothing between {} ? Hash the whole key. + if (e == keylen || e == s + 1) return crc16(k, keylen) & SHARDS; + + // If we are here there is both a { and a } on its right. Hash + // what is in the middle between { and }. + return crc16(k + s + 1, e - s - 1) & SHARDS; +} + +Slot ShardsPool::_slot() const { + static thread_local std::default_random_engine engine; + + std::uniform_int_distribution uniform_dist(0, SHARDS); + + return uniform_dist(engine); +} + +ConnectionPoolSPtr& ShardsPool::_get_pool(Slot slot) { + auto shards_iter = _shards.lower_bound(SlotRange{slot, slot}); + if (shards_iter == _shards.end() || slot < shards_iter->first.min) { + throw Error("Slot is out of range: " + std::to_string(slot)); + } + + const auto &node = shards_iter->second; + + auto node_iter = _pools.find(node); + if (node_iter == _pools.end()) { + throw Error("Slot is NOT covered: " + std::to_string(slot)); + } + + return node_iter->second; +} + +GuardedConnection ShardsPool::_fetch(Slot slot) { + std::lock_guard lock(_mutex); + + auto &pool = _get_pool(slot); + + assert(pool); + + return GuardedConnection(pool); +} + +ConnectionOptions ShardsPool::_connection_options(Slot slot) { + std::lock_guard lock(_mutex); + + auto &pool = _get_pool(slot); + + assert(pool); + + return pool->connection_options(); +} + +auto ShardsPool::_add_node(const Node &node) -> NodeMap::iterator { + auto opts = _connection_opts; + opts.host = node.host; + opts.port = node.port; + + return _pools.emplace(node, std::make_shared(_pool_opts, opts)).first; +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.h new file mode 100644 index 000000000..1184806e9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.h @@ -0,0 +1,137 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H + +#include +#include +#include +#include +#include +#include "reply.h" +#include "connection_pool.h" +#include "shards.h" + +namespace sw { + +namespace redis { + +using ConnectionPoolSPtr = std::shared_ptr; + +class GuardedConnection { +public: + GuardedConnection(const ConnectionPoolSPtr &pool) : _pool(pool), + _connection(_pool->fetch()) { + assert(!_connection.broken()); + } + + GuardedConnection(const GuardedConnection &) = delete; + GuardedConnection& operator=(const GuardedConnection &) = delete; + + GuardedConnection(GuardedConnection &&) = default; + GuardedConnection& operator=(GuardedConnection &&) = default; + + ~GuardedConnection() { + _pool->release(std::move(_connection)); + } + + Connection& connection() { + return _connection; + } + +private: + ConnectionPoolSPtr _pool; + Connection _connection; +}; + +class ShardsPool { +public: + ShardsPool() = default; + + ShardsPool(const ShardsPool &that) = delete; + ShardsPool& operator=(const ShardsPool &that) = delete; + + ShardsPool(ShardsPool &&that); + ShardsPool& operator=(ShardsPool &&that); + + ~ShardsPool() = default; + + ShardsPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + // Fetch a connection by key. + GuardedConnection fetch(const StringView &key); + + // Randomly pick a connection. + GuardedConnection fetch(); + + // Fetch a connection by node. + GuardedConnection fetch(const Node &node); + + void update(); + + ConnectionOptions connection_options(const StringView &key); + + ConnectionOptions connection_options(); + +private: + void _move(ShardsPool &&that); + + void _init_pool(const Shards &shards); + + Shards _cluster_slots(Connection &connection) const; + + ReplyUPtr _cluster_slots_command(Connection &connection) const; + + Shards _parse_reply(redisReply &reply) const; + + std::pair _parse_slot_info(redisReply &reply) const; + + // Get slot by key. + std::size_t _slot(const StringView &key) const; + + // Randomly pick a slot. + std::size_t _slot() const; + + ConnectionPoolSPtr& _get_pool(Slot slot); + + GuardedConnection _fetch(Slot slot); + + ConnectionOptions _connection_options(Slot slot); + + using NodeMap = std::unordered_map; + + NodeMap::iterator _add_node(const Node &node); + + ConnectionPoolOptions _pool_opts; + + ConnectionOptions _connection_opts; + + Shards _shards; + + NodeMap _pools; + + std::mutex _mutex; + + static const std::size_t SHARDS = 16383; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.cpp new file mode 100644 index 000000000..b699b02f0 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.cpp @@ -0,0 +1,222 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "subscriber.h" +#include + +namespace sw { + +namespace redis { + +const Subscriber::TypeIndex Subscriber::_msg_type_index = { + {"message", MsgType::MESSAGE}, + {"pmessage", MsgType::PMESSAGE}, + {"subscribe", MsgType::SUBSCRIBE}, + {"unsubscribe", MsgType::UNSUBSCRIBE}, + {"psubscribe", MsgType::PSUBSCRIBE}, + {"punsubscribe", MsgType::PUNSUBSCRIBE} +}; + +Subscriber::Subscriber(Connection connection) : _connection(std::move(connection)) {} + +void Subscriber::subscribe(const StringView &channel) { + _check_connection(); + + // TODO: cmd::subscribe DOES NOT send the subscribe message to Redis. + // In fact, it puts the command to network buffer. + // So we need a queue to record these sub or unsub commands, and + // ensure that before stopping the subscriber, all these commands + // have really been sent to Redis. + cmd::subscribe(_connection, channel); +} + +void Subscriber::unsubscribe() { + _check_connection(); + + cmd::unsubscribe(_connection); +} + +void Subscriber::unsubscribe(const StringView &channel) { + _check_connection(); + + cmd::unsubscribe(_connection, channel); +} + +void Subscriber::psubscribe(const StringView &pattern) { + _check_connection(); + + cmd::psubscribe(_connection, pattern); +} + +void Subscriber::punsubscribe() { + _check_connection(); + + cmd::punsubscribe(_connection); +} + +void Subscriber::punsubscribe(const StringView &pattern) { + _check_connection(); + + cmd::punsubscribe(_connection, pattern); +} + +void Subscriber::consume() { + _check_connection(); + + ReplyUPtr reply; + try { + reply = _connection.recv(); + } catch (const TimeoutError &) { + _connection.reset(); + throw; + } + + assert(reply); + + if (!reply::is_array(*reply) || reply->elements < 1 || reply->element == nullptr) { + throw ProtoError("Invalid subscribe message"); + } + + auto type = _msg_type(reply->element[0]); + switch (type) { + case MsgType::MESSAGE: + _handle_message(*reply); + break; + + case MsgType::PMESSAGE: + _handle_pmessage(*reply); + break; + + case MsgType::SUBSCRIBE: + case MsgType::UNSUBSCRIBE: + case MsgType::PSUBSCRIBE: + case MsgType::PUNSUBSCRIBE: + _handle_meta(type, *reply); + break; + + default: + assert(false); + } +} + +Subscriber::MsgType Subscriber::_msg_type(redisReply *reply) const { + if (reply == nullptr) { + throw ProtoError("Null type reply."); + } + + auto type = reply::parse(*reply); + + auto iter = _msg_type_index.find(type); + if (iter == _msg_type_index.end()) { + throw ProtoError("Invalid message type."); + } + + return iter->second; +} + +void Subscriber::_check_connection() { + if (_connection.broken()) { + throw Error("Connection is broken"); + } +} + +void Subscriber::_handle_message(redisReply &reply) { + if (_msg_callback == nullptr) { + return; + } + + if (reply.elements != 3) { + throw ProtoError("Expect 3 sub replies"); + } + + assert(reply.element != nullptr); + + auto *channel_reply = reply.element[1]; + if (channel_reply == nullptr) { + throw ProtoError("Null channel reply"); + } + auto channel = reply::parse(*channel_reply); + + auto *msg_reply = reply.element[2]; + if (msg_reply == nullptr) { + throw ProtoError("Null message reply"); + } + auto msg = reply::parse(*msg_reply); + + _msg_callback(std::move(channel), std::move(msg)); +} + +void Subscriber::_handle_pmessage(redisReply &reply) { + if (_pmsg_callback == nullptr) { + return; + } + + if (reply.elements != 4) { + throw ProtoError("Expect 4 sub replies"); + } + + assert(reply.element != nullptr); + + auto *pattern_reply = reply.element[1]; + if (pattern_reply == nullptr) { + throw ProtoError("Null pattern reply"); + } + auto pattern = reply::parse(*pattern_reply); + + auto *channel_reply = reply.element[2]; + if (channel_reply == nullptr) { + throw ProtoError("Null channel reply"); + } + auto channel = reply::parse(*channel_reply); + + auto *msg_reply = reply.element[3]; + if (msg_reply == nullptr) { + throw ProtoError("Null message reply"); + } + auto msg = reply::parse(*msg_reply); + + _pmsg_callback(std::move(pattern), std::move(channel), std::move(msg)); +} + +void Subscriber::_handle_meta(MsgType type, redisReply &reply) { + if (_meta_callback == nullptr) { + return; + } + + if (reply.elements != 3) { + throw ProtoError("Expect 3 sub replies"); + } + + assert(reply.element != nullptr); + + auto *channel_reply = reply.element[1]; + if (channel_reply == nullptr) { + throw ProtoError("Null channel reply"); + } + auto channel = reply::parse(*channel_reply); + + auto *num_reply = reply.element[2]; + if (num_reply == nullptr) { + throw ProtoError("Null num reply"); + } + auto num = reply::parse(*num_reply); + + _meta_callback(type, std::move(channel), num); +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.h new file mode 100644 index 000000000..8b7c5cfb4 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.h @@ -0,0 +1,231 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H +#define SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H + +#include +#include +#include +#include "connection.h" +#include "reply.h" +#include "command.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +// @NOTE: Subscriber is NOT thread-safe. +// Subscriber uses callbacks to handle messages. There are 6 kinds of messages: +// 1) MESSAGE: message sent to a channel. +// 2) PMESSAGE: message sent to channels of a given pattern. +// 3) SUBSCRIBE: meta message sent when we successfully subscribe to a channel. +// 4) UNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel. +// 5) PSUBSCRIBE: meta message sent when we successfully subscribe to a channel pattern. +// 6) PUNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel pattern. +// +// Use Subscriber::on_message(MsgCallback) to set the callback function for message of +// *MESSAGE* type, and the callback interface is: +// void (std::string channel, std::string msg) +// +// Use Subscriber::on_pmessage(PatternMsgCallback) to set the callback function for message of +// *PMESSAGE* type, and the callback interface is: +// void (std::string pattern, std::string channel, std::string msg) +// +// Messages of other types are called *META MESSAGE*, they have the same callback interface. +// Use Subscriber::on_meta(MetaCallback) to set the callback function: +// void (Subscriber::MsgType type, OptionalString channel, long long num) +// +// NOTE: If we haven't subscribe/psubscribe to any channel/pattern, and try to +// unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all +// channels/patterns, *channel* will be null. So the second parameter of meta callback +// is of type *OptionalString*. +// +// All these callback interfaces pass std::string by value, and you can take their ownership +// (i.e. std::move) safely. +// +// If you don't set callback for a specific kind of message, Subscriber::consume() will +// receive the message, and ignore it, i.e. no callback will be called. +class Subscriber { +public: + Subscriber(const Subscriber &) = delete; + Subscriber& operator=(const Subscriber &) = delete; + + Subscriber(Subscriber &&) = default; + Subscriber& operator=(Subscriber &&) = default; + + ~Subscriber() = default; + + enum class MsgType { + SUBSCRIBE, + UNSUBSCRIBE, + PSUBSCRIBE, + PUNSUBSCRIBE, + MESSAGE, + PMESSAGE + }; + + template + void on_message(MsgCb msg_callback); + + template + void on_pmessage(PMsgCb pmsg_callback); + + template + void on_meta(MetaCb meta_callback); + + void subscribe(const StringView &channel); + + template + void subscribe(Input first, Input last); + + template + void subscribe(std::initializer_list channels) { + subscribe(channels.begin(), channels.end()); + } + + void unsubscribe(); + + void unsubscribe(const StringView &channel); + + template + void unsubscribe(Input first, Input last); + + template + void unsubscribe(std::initializer_list channels) { + unsubscribe(channels.begin(), channels.end()); + } + + void psubscribe(const StringView &pattern); + + template + void psubscribe(Input first, Input last); + + template + void psubscribe(std::initializer_list channels) { + psubscribe(channels.begin(), channels.end()); + } + + void punsubscribe(); + + void punsubscribe(const StringView &channel); + + template + void punsubscribe(Input first, Input last); + + template + void punsubscribe(std::initializer_list channels) { + punsubscribe(channels.begin(), channels.end()); + } + + void consume(); + +private: + friend class Redis; + + friend class RedisCluster; + + explicit Subscriber(Connection connection); + + MsgType _msg_type(redisReply *reply) const; + + void _check_connection(); + + void _handle_message(redisReply &reply); + + void _handle_pmessage(redisReply &reply); + + void _handle_meta(MsgType type, redisReply &reply); + + using MsgCallback = std::function; + + using PatternMsgCallback = std::function; + + using MetaCallback = std::function; + + using TypeIndex = std::unordered_map; + static const TypeIndex _msg_type_index; + + Connection _connection; + + MsgCallback _msg_callback = nullptr; + + PatternMsgCallback _pmsg_callback = nullptr; + + MetaCallback _meta_callback = nullptr; +}; + +template +void Subscriber::on_message(MsgCb msg_callback) { + _msg_callback = msg_callback; +} + +template +void Subscriber::on_pmessage(PMsgCb pmsg_callback) { + _pmsg_callback = pmsg_callback; +} + +template +void Subscriber::on_meta(MetaCb meta_callback) { + _meta_callback = meta_callback; +} + +template +void Subscriber::subscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::subscribe_range(_connection, first, last); +} + +template +void Subscriber::unsubscribe(Input first, Input last) { + _check_connection(); + + cmd::unsubscribe_range(_connection, first, last); +} + +template +void Subscriber::psubscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::psubscribe_range(_connection, first, last); +} + +template +void Subscriber::punsubscribe(Input first, Input last) { + _check_connection(); + + cmd::punsubscribe_range(_connection, first, last); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.cpp new file mode 100644 index 000000000..faa1bd178 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.cpp @@ -0,0 +1,123 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "transaction.h" +#include "command.h" + +namespace sw { + +namespace redis { + +std::vector TransactionImpl::exec(Connection &connection, std::size_t cmd_num) { + _close_transaction(); + + _get_queued_replies(connection, cmd_num); + + return _exec(connection); +} + +void TransactionImpl::discard(Connection &connection, std::size_t cmd_num) { + _close_transaction(); + + _get_queued_replies(connection, cmd_num); + + _discard(connection); +} + +void TransactionImpl::_open_transaction(Connection &connection) { + assert(!_in_transaction); + + cmd::multi(connection); + auto reply = connection.recv(); + auto status = reply::to_status(*reply); + if (status != "OK") { + throw Error("Failed to open transaction: " + status); + } + + _in_transaction = true; +} + +void TransactionImpl::_close_transaction() { + if (!_in_transaction) { + throw Error("No command in transaction"); + } + + _in_transaction = false; +} + +void TransactionImpl::_get_queued_reply(Connection &connection) { + auto reply = connection.recv(); + auto status = reply::to_status(*reply); + if (status != "QUEUED") { + throw Error("Invalid QUEUED reply: " + status); + } +} + +void TransactionImpl::_get_queued_replies(Connection &connection, std::size_t cmd_num) { + if (_piped) { + // Get all QUEUED reply + while (cmd_num > 0) { + _get_queued_reply(connection); + + --cmd_num; + } + } +} + +std::vector TransactionImpl::_exec(Connection &connection) { + cmd::exec(connection); + + auto reply = connection.recv(); + + if (reply::is_nil(*reply)) { + // Execution has been aborted, i.e. watched key has been modified. + throw WatchError(); + } + + if (!reply::is_array(*reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply->element == nullptr || reply->elements == 0) { + // Since we don't allow EXEC without any command, this ARRAY reply + // should NOT be null or empty. + throw ProtoError("Null ARRAY reply"); + } + + std::vector replies; + for (std::size_t idx = 0; idx != reply->elements; ++idx) { + auto *sub_reply = reply->element[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null sub reply"); + } + + auto r = ReplyUPtr(sub_reply); + reply->element[idx] = nullptr; + replies.push_back(std::move(r)); + } + + return replies; +} + +void TransactionImpl::_discard(Connection &connection) { + cmd::discard(connection); + auto reply = connection.recv(); + reply::parse(*reply); +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.h new file mode 100644 index 000000000..f19f24889 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.h @@ -0,0 +1,77 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TRANSACTION_H +#define SEWENEW_REDISPLUSPLUS_TRANSACTION_H + +#include +#include +#include "connection.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +class TransactionImpl { +public: + explicit TransactionImpl(bool piped) : _piped(piped) {} + + template + void command(Connection &connection, Cmd cmd, Args &&...args); + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t cmd_num); + +private: + void _open_transaction(Connection &connection); + + void _close_transaction(); + + void _get_queued_reply(Connection &connection); + + void _get_queued_replies(Connection &connection, std::size_t cmd_num); + + std::vector _exec(Connection &connection); + + void _discard(Connection &connection); + + bool _in_transaction = false; + + bool _piped; +}; + +template +void TransactionImpl::command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + if (!_in_transaction) { + _open_transaction(connection); + } + + cmd(connection, std::forward(args)...); + + if (!_piped) { + _get_queued_reply(connection); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TRANSACTION_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/utils.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/utils.h new file mode 100644 index 000000000..e29e64e14 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/utils.h @@ -0,0 +1,269 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_UTILS_H +#define SEWENEW_REDISPLUSPLUS_UTILS_H + +#include +#include +#include + +namespace sw { + +namespace redis { + +// By now, not all compilers support std::string_view, +// so we make our own implementation. +class StringView { +public: + constexpr StringView() noexcept = default; + + constexpr StringView(const char *data, std::size_t size) : _data(data), _size(size) {} + + StringView(const char *data) : _data(data), _size(std::strlen(data)) {} + + StringView(const std::string &str) : _data(str.data()), _size(str.size()) {} + + constexpr StringView(const StringView &) noexcept = default; + + StringView& operator=(const StringView &) noexcept = default; + + constexpr const char* data() const noexcept { + return _data; + } + + constexpr std::size_t size() const noexcept { + return _size; + } + +private: + const char *_data = nullptr; + std::size_t _size = 0; +}; + +template +class Optional { +public: + Optional() = default; + + Optional(const Optional &) = default; + Optional& operator=(const Optional &) = default; + + Optional(Optional &&) = default; + Optional& operator=(Optional &&) = default; + + ~Optional() = default; + + template + explicit Optional(Args &&...args) : _value(true, T(std::forward(args)...)) {} + + explicit operator bool() const { + return _value.first; + } + + T& value() { + return _value.second; + } + + const T& value() const { + return _value.second; + } + + T* operator->() { + return &(_value.second); + } + + const T* operator->() const { + return &(_value.second); + } + + T& operator*() { + return _value.second; + } + + const T& operator*() const { + return _value.second; + } + +private: + std::pair _value; +}; + +using OptionalString = Optional; + +using OptionalLongLong = Optional; + +using OptionalDouble = Optional; + +using OptionalStringPair = Optional>; + +template +struct IsKvPair : std::false_type {}; + +template +struct IsKvPair> : std::true_type {}; + +template +using Void = void; + +template > +struct IsInserter : std::false_type {}; + +template +//struct IsInserter> : std::true_type {}; +struct IsInserter::value>::type> + : std::true_type {}; + +template > +struct IterType { + using type = typename std::iterator_traits::value_type; +}; + +template +//struct IterType> { +struct IterType::value>::type> { + typename std::enable_if::value>::type> { + using type = typename std::decay::type; +}; + +template > +struct IsIter : std::false_type {}; + +template +struct IsIter::value>::type> : std::true_type {}; + +template +//struct IsIter::iterator_category>> +struct IsIter::value_type>::value>::type> + : std::integral_constant::value> {}; + +template +struct IsKvPairIter : IsKvPair::type> {}; + +template +struct TupleWithType : std::false_type {}; + +template +struct TupleWithType> : std::false_type {}; + +template +struct TupleWithType> : TupleWithType> {}; + +template +struct TupleWithType> : std::true_type {}; + +template +struct IndexSequence {}; + +template +struct MakeIndexSequence : MakeIndexSequence {}; + +template +struct MakeIndexSequence<0, Is...> : IndexSequence {}; + +// NthType and NthValue are taken from +// https://stackoverflow.com/questions/14261183 +template +struct NthType {}; + +template +struct NthType<0, Arg, Args...> { + using type = Arg; +}; + +template +struct NthType { + using type = typename NthType::type; +}; + +template +struct LastType { + using type = typename NthType::type; +}; + +struct InvalidLastType {}; + +template <> +struct LastType<> { + using type = InvalidLastType; +}; + +template +auto NthValue(Arg &&arg, Args &&...) + -> typename std::enable_if<(I == 0), decltype(std::forward(arg))>::type { + return std::forward(arg); +} + +template +auto NthValue(Arg &&, Args &&...args) + -> typename std::enable_if<(I > 0), + decltype(std::forward::type>( + std::declval::type>()))>::type { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template +auto LastValue(Args &&...args) + -> decltype(std::forward::type>( + std::declval::type>())) { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template > +struct HasPushBack : std::false_type {}; + +template +struct HasPushBack().push_back(std::declval()) + )>::value>::type> : std::true_type {}; + +template > +struct HasInsert : std::false_type {}; + +template +struct HasInsert().insert(std::declval(), + std::declval())), + typename T::iterator>::value>::type> : std::true_type {}; + +template +struct IsSequenceContainer + : std::integral_constant::value + && !std::is_same::type, std::string>::value> {}; + +template +struct IsAssociativeContainer + : std::integral_constant::value && !HasPushBack::value> {}; + +uint16_t crc16(const char *buf, int len); + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_UTILS_H diff --git a/ext/redis-plus-plus-1.1.1/test/CMakeLists.txt b/ext/redis-plus-plus-1.1.1/test/CMakeLists.txt new file mode 100644 index 000000000..861e6ff88 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/CMakeLists.txt @@ -0,0 +1,33 @@ +project(test_redis++) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + cmake_minimum_required(VERSION 3.0.0) +else() + cmake_minimum_required(VERSION 2.8.0) +endif() + +set(PROJECT_SOURCE_DIR ${PROJECT_SOURCE_DIR}/src/sw/redis++) + +file(GLOB PROJECT_SOURCE_FILES "${PROJECT_SOURCE_DIR}/*.cpp") + +add_executable(${PROJECT_NAME} ${PROJECT_SOURCE_FILES}) + +# hiredis dependency +find_path(HIREDIS_HEADER hiredis) +target_include_directories(${PROJECT_NAME} PUBLIC ${HIREDIS_HEADER}) + +find_library(HIREDIS_STATIC_LIB libhiredis.a) +target_link_libraries(${PROJECT_NAME} ${HIREDIS_STATIC_LIB}) + +# redis++ dependency +target_include_directories(${PROJECT_NAME} PUBLIC ../src) +set(REDIS_PLUS_PLUS_LIB ${CMAKE_CURRENT_BINARY_DIR}/../lib/libredis++.a) + +## solaris socket dependency +IF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)" ) + target_link_libraries(${PROJECT_NAME} -lsocket) +ENDIF(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)" ) + +find_package(Threads REQUIRED) + +target_link_libraries(${PROJECT_NAME} ${REDIS_PLUS_PLUS_LIB} ${CMAKE_THREAD_LIBS_INIT}) diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.h new file mode 100644 index 000000000..309bc6863 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.h @@ -0,0 +1,83 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +struct BenchmarkOptions { + std::size_t pool_size = 5; + std::size_t thread_num = 10; + std::size_t total_request_num = 100000; + std::size_t key_len = 10; + std::size_t val_len = 10; +}; + +template +class BenchmarkTest { +public: + BenchmarkTest(const BenchmarkOptions &opts, RedisInstance &instance); + + ~BenchmarkTest() { + _cleanup(); + } + + void run(); + +private: + template + void _run(const std::string &title, Func &&func); + + template + std::size_t _run(Func &&func, std::size_t request_num); + + void _test_get(); + + std::vector _gen_keys() const; + + std::string _gen_value() const; + + void _cleanup(); + + const std::string& _key(std::size_t idx) const { + return _keys[idx % _keys.size()]; + } + + BenchmarkOptions _opts; + + RedisInstance &_redis; + + std::vector _keys; + + std::string _value; +}; + +} + +} + +} + +#include "benchmark_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.hpp new file mode 100644 index 000000000..eaf50ec50 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.hpp @@ -0,0 +1,178 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_HPP + +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +BenchmarkTest::BenchmarkTest(const BenchmarkOptions &opts, + RedisInstance &instance) : _opts(opts), _redis(instance) { + REDIS_ASSERT(_opts.pool_size > 0 + && _opts.thread_num > 0 + && _opts.total_request_num > 0 + && _opts.key_len > 0 + && _opts.val_len > 0, + "Invalid benchmark test options."); + + _keys = _gen_keys(); + _value = _gen_value(); +} + +template +void BenchmarkTest::run() { + _cleanup(); + + _run("SET key value", [this](std::size_t idx) { this->_redis.set(this->_key(idx), _value); }); + + _run("GET key", [this](std::size_t idx) { + auto res = this->_redis.get(this->_key(idx)); + (void)res; + }); + + _cleanup(); + + _run("LPUSH key value", [this](std::size_t idx) { + this->_redis.lpush(this->_key(idx), _value); + }); + + _run("LRANGE key 0 10", [this](std::size_t idx) { + std::vector res; + res.reserve(10); + this->_redis.lrange(this->_key(idx), 0, 10, std::back_inserter(res)); + }); + + _run("LPOP key", [this](std::size_t idx) { + auto res = this->_redis.lpop(this->_key(idx)); + (void)res; + }); + + _cleanup(); + + _run("INCR key", [this](std::size_t idx) { + auto num = this->_redis.incr(this->_key(idx)); + (void)num; + }); + + _cleanup(); + + _run("SADD key member", [this](std::size_t idx) { + auto num = this->_redis.sadd(this->_key(idx), _value); + (void)num; + }); + + _run("SPOP key", [this](std::size_t idx) { + auto res = this->_redis.spop(this->_key(idx)); + (void)res; + }); + + _cleanup(); +} + +template +template +void BenchmarkTest::_run(const std::string &title, Func &&func) { + auto thread_num = _opts.thread_num; + auto requests_per_thread = _opts.total_request_num / thread_num; + auto total_request_num = requests_per_thread * thread_num; + std::vector> res; + res.reserve(thread_num); + res.push_back(std::async(std::launch::async, + [this](Func &&func, std::size_t request_num) { + return this->_run(std::forward(func), request_num); + }, + std::forward(func), + requests_per_thread)); + + auto total_in_msec = 0; + for (auto &fut : res) { + total_in_msec += fut.get(); + } + + auto total_in_sec = total_in_msec * 1.0 / 1000; + + auto avg = total_in_msec * 1.0 / total_request_num; + + auto ops = static_cast(1000 / avg); + + std::cout << "-----" << title << "-----" << std::endl; + std::cout << total_request_num << " requests cost " << total_in_sec << " seconds" << std::endl; + std::cout << ops << " requests per second" << std::endl; +} + +template +template +std::size_t BenchmarkTest::_run(Func &&func, std::size_t request_num) { + auto start = std::chrono::steady_clock::now(); + + for (auto idx = 0U; idx != request_num; ++idx) { + func(idx); + } + + auto stop = std::chrono::steady_clock::now(); + + return std::chrono::duration_cast(stop - start).count(); +} + +template +std::vector BenchmarkTest::_gen_keys() const { + const auto KEY_NUM = 100; + std::vector res; + res.reserve(KEY_NUM); + std::default_random_engine engine(std::random_device{}()); + std::uniform_int_distribution uniform_dist(0, 255); + for (auto i = 0; i != KEY_NUM; ++i) { + std::string str; + str.reserve(_opts.key_len); + for (std::size_t j = 0; j != _opts.key_len; ++j) { + str.push_back(static_cast(uniform_dist(engine))); + } + res.push_back(str); + } + + return res; +} + +template +std::string BenchmarkTest::_gen_value() const { + return std::string(_opts.val_len, 'x'); +} + +template +void BenchmarkTest::_cleanup() { + for (const auto &key : _keys) { + _redis.del(key); + } +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.h new file mode 100644 index 000000000..2127257c3 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.h @@ -0,0 +1,49 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class ConnectionCmdTest { +public: + explicit ConnectionCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _run(Redis &redis); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "connection_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.hpp new file mode 100644 index 000000000..90e7c313b --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.hpp @@ -0,0 +1,50 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_HPP + +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void ConnectionCmdTest::run() { + cluster_specializing_test(*this, &ConnectionCmdTest::_run, _redis); +} + +template +void ConnectionCmdTest::_run(Redis &instance) { + auto message = std::string("hello"); + + REDIS_ASSERT(instance.echo(message) == message, "failed to test echo"); + + REDIS_ASSERT(instance.ping() == "PONG", "failed to test ping"); + + REDIS_ASSERT(instance.ping(message) == message, "failed to test ping"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.h new file mode 100644 index 000000000..41b9795fa --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.h @@ -0,0 +1,47 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class GeoCmdTest { +public: + explicit GeoCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + RedisInstance &_redis; +}; + +} + +} + +} + +#include "geo_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.hpp new file mode 100644 index 000000000..866fafe10 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.hpp @@ -0,0 +1,149 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_HPP + +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void GeoCmdTest::run() { + auto key = test_key("geo"); + auto dest = test_key("dest"); + + KeyDeleter deleter(_redis, {key, dest}); + + auto members = { + std::make_tuple("m1", 10.0, 11.0), + std::make_tuple("m2", 10.1, 11.1), + std::make_tuple("m3", 10.2, 11.2) + }; + + REDIS_ASSERT(_redis.geoadd(key, std::make_tuple("m1", 10.0, 11.0)) == 1, + "failed to test geoadd"); + REDIS_ASSERT(_redis.geoadd(key, members) == 2, "failed to test geoadd"); + + auto dist = _redis.geodist(key, "m1", "m4", GeoUnit::KM); + REDIS_ASSERT(!dist, "failed to test geodist with nonexistent member"); + + std::vector hashes; + _redis.geohash(key, {"m1", "m4"}, std::back_inserter(hashes)); + REDIS_ASSERT(hashes.size() == 2, "failed to test geohash"); + REDIS_ASSERT(bool(hashes[0]) && *(hashes[0]) == "s1zned3z8u0" && !(hashes[1]), + "failed to test geohash"); + hashes.clear(); + _redis.geohash(key, {"m4"}, std::back_inserter(hashes)); + REDIS_ASSERT(hashes.size() == 1 && !(hashes[0]), "failed to test geohash"); + + std::vector>> pos; + _redis.geopos(key, {"m4"}, std::back_inserter(pos)); + REDIS_ASSERT(pos.size() == 1 && !(pos[0]), "failed to test geopos"); + + auto num = _redis.georadius(key, + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + dest, + false, + 10); + REDIS_ASSERT(bool(num) && *num == 3, "failed to test georadius with store option"); + + std::vector mems; + _redis.georadius(key, + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(mems)); + REDIS_ASSERT(mems.size() == 3, "failed to test georadius with no option"); + + std::vector> with_dist; + _redis.georadius(key, + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(with_dist)); + REDIS_ASSERT(with_dist.size() == 3, "failed to test georadius with dist"); + + std::vector>> with_dist_coord; + _redis.georadius(key, + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(with_dist_coord)); + REDIS_ASSERT(with_dist_coord.size() == 3, "failed to test georadius with dist and coord"); + + num = _redis.georadiusbymember(key, + "m1", + 100, + GeoUnit::KM, + dest, + false, + 10); + REDIS_ASSERT(bool(num) && *num == 3, "failed to test georadiusbymember with store option"); + + mems.clear(); + _redis.georadiusbymember(key, + "m1", + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(mems)); + REDIS_ASSERT(mems.size() == 3, "failed to test georadiusbymember with no option"); + + with_dist.clear(); + _redis.georadiusbymember(key, + "m1", + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(with_dist)); + REDIS_ASSERT(with_dist.size() == 3, "failed to test georadiusbymember with dist"); + + with_dist_coord.clear(); + _redis.georadiusbymember(key, + "m1", + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(with_dist_coord)); + REDIS_ASSERT(with_dist_coord.size() == 3, + "failed to test georadiusbymember with dist and coord"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.h new file mode 100644 index 000000000..60791c8b9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.h @@ -0,0 +1,55 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class HashCmdTest { +public: + explicit HashCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_hash(); + + void _test_hash_batch(); + + void _test_numeric(); + + void _test_hscan(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "hash_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.hpp new file mode 100644 index 000000000..42c309f4e --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.hpp @@ -0,0 +1,177 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_HPP + +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void HashCmdTest::run() { + _test_hash(); + + _test_hash_batch(); + + _test_numeric(); + + _test_hscan(); +} + +template +void HashCmdTest::_test_hash() { + auto key = test_key("hash"); + + KeyDeleter deleter(_redis, key); + + auto f1 = std::string("f1"); + auto v1 = std::string("v1"); + auto f2 = std::string("f2"); + auto v2 = std::string("v2"); + auto f3 = std::string("f3"); + auto v3 = std::string("v3"); + + REDIS_ASSERT(_redis.hset(key, f1, v1), "failed to test hset"); + REDIS_ASSERT(!_redis.hset(key, f1, v2), "failed to test hset with exist field"); + + auto res = _redis.hget(key, f1); + REDIS_ASSERT(res && *res == v2, "failed to test hget"); + + REDIS_ASSERT(_redis.hsetnx(key, f2, v1), "failed to test hsetnx"); + REDIS_ASSERT(!_redis.hsetnx(key, f2, v2), "failed to test hsetnx with exist field"); + + res = _redis.hget(key, f2); + REDIS_ASSERT(res && *res == v1, "failed to test hget"); + + REDIS_ASSERT(!_redis.hexists(key, f3), "failed to test hexists"); + REDIS_ASSERT(_redis.hset(key, std::make_pair(f3, v3)), "failed to test hset"); + REDIS_ASSERT(_redis.hexists(key, f3), "failed to test hexists"); + + REDIS_ASSERT(_redis.hlen(key) == 3, "failed to test hlen"); + REDIS_ASSERT(_redis.hstrlen(key, f1) == static_cast(v1.size()), + "failed to test hstrlen"); + + REDIS_ASSERT(_redis.hdel(key, f1) == 1, "failed to test hdel"); + REDIS_ASSERT(_redis.hdel(key, {f1, f2, f3}) == 2, "failed to test hdel range"); +} + +template +void HashCmdTest::_test_hash_batch() { + auto key = test_key("hash"); + + KeyDeleter deleter(_redis, key); + + auto f1 = std::string("f1"); + auto v1 = std::string("v1"); + auto f2 = std::string("f2"); + auto v2 = std::string("v2"); + auto f3 = std::string("f3"); + + _redis.hmset(key, {std::make_pair(f1, v1), + std::make_pair(f2, v2)}); + + std::vector fields; + _redis.hkeys(key, std::back_inserter(fields)); + REDIS_ASSERT(fields.size() == 2, "failed to test hkeys"); + + std::vector vals; + _redis.hvals(key, std::back_inserter(vals)); + REDIS_ASSERT(vals.size() == 2, "failed to test hvals"); + + std::unordered_map items; + _redis.hgetall(key, std::inserter(items, items.end())); + REDIS_ASSERT(items.size() == 2 && items[f1] == v1 && items[f2] == v2, + "failed to test hgetall"); + + std::vector> item_vec; + _redis.hgetall(key, std::back_inserter(item_vec)); + REDIS_ASSERT(item_vec.size() == 2, "failed to test hgetall"); + + std::vector res; + _redis.hmget(key, {f1, f2, f3}, std::back_inserter(res)); + REDIS_ASSERT(res.size() == 3 + && bool(res[0]) && *(res[0]) == v1 + && bool(res[1]) && *(res[1]) == v2 + && !res[2], + "failed to test hmget"); +} + +template +void HashCmdTest::_test_numeric() { + auto key = test_key("numeric"); + + KeyDeleter deleter(_redis, key); + + auto field = "field"; + + REDIS_ASSERT(_redis.hincrby(key, field, 1) == 1, "failed to test hincrby"); + REDIS_ASSERT(_redis.hincrby(key, field, -1) == 0, "failed to test hincrby"); + REDIS_ASSERT(_redis.hincrbyfloat(key, field, 1.5) == 1.5, "failed to test hincrbyfloat"); +} + +template +void HashCmdTest::_test_hscan() { + auto key = test_key("hscan"); + + KeyDeleter deleter(_redis, key); + + auto items = std::unordered_map{ + std::make_pair("f1", "v1"), + std::make_pair("f2", "v2"), + std::make_pair("f3", "v3"), + }; + + _redis.hmset(key, items.begin(), items.end()); + + std::unordered_map item_map; + auto cursor = 0; + while (true) { + cursor = _redis.hscan(key, cursor, "f*", 2, std::inserter(item_map, item_map.end())); + if (cursor == 0) { + break; + } + } + + REDIS_ASSERT(item_map == items, "failed to test hscan with pattern and count"); + + std::vector> item_vec; + cursor = 0; + while (true) { + cursor = _redis.hscan(key, cursor, std::back_inserter(item_vec)); + if (cursor == 0) { + break; + } + } + + REDIS_ASSERT(item_vec.size() == items.size(), "failed to test hscan"); + for (const auto &ele : item_vec) { + REDIS_ASSERT(items.find(ele.first) != items.end(), "failed to test hscan"); + } +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.h new file mode 100644 index 000000000..50e454b23 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.h @@ -0,0 +1,47 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class HyperloglogCmdTest { +public: + explicit HyperloglogCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + RedisInstance &_redis; +}; + +} + +} + +} + +#include "hyperloglog_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.hpp new file mode 100644 index 000000000..09775255f --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.hpp @@ -0,0 +1,67 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_HPP + +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void HyperloglogCmdTest::run() { + auto k1 = test_key("k1"); + auto k2 = test_key("k2"); + auto k3 = test_key("k3"); + + KeyDeleter deleter(_redis, {k1, k2, k3}); + + _redis.pfadd(k1, "a"); + auto members1 = {"b", "c", "d", "e", "f", "g"}; + _redis.pfadd(k1, members1); + + auto cnt = _redis.pfcount(k1); + auto err = cnt * 1.0 / (1 + members1.size()); + REDIS_ASSERT(err < 1.02 && err > 0.98, "failed to test pfadd and pfcount"); + + auto members2 = {"a", "b", "c", "h", "i", "j", "k"}; + _redis.pfadd(k2, members2); + auto total = 1 + members1.size() + members2.size() - 3; + + cnt = _redis.pfcount({k1, k2}); + err = cnt * 1.0 / total; + REDIS_ASSERT(err < 1.02 && err > 0.98, "failed to test pfcount"); + + _redis.pfmerge(k3, {k1, k2}); + cnt = _redis.pfcount(k3); + err = cnt * 1.0 / total; + REDIS_ASSERT(err < 1.02 && err > 0.98, "failed to test pfcount"); + + _redis.pfmerge(k3, k1); + REDIS_ASSERT(cnt == _redis.pfcount(k3), "failed to test pfmerge"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.h new file mode 100644 index 000000000..54d25ca28 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.h @@ -0,0 +1,55 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class KeysCmdTest { +public: + explicit KeysCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_key(); + + void _test_randomkey(Redis &instance); + + void _test_ttl(); + + void _test_scan(Redis &instance); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "keys_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.hpp new file mode 100644 index 000000000..de21f8d18 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.hpp @@ -0,0 +1,166 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_HPP + +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void KeysCmdTest::run() { + _test_key(); + + cluster_specializing_test(*this, &KeysCmdTest::_test_randomkey, _redis); + + _test_ttl(); + + cluster_specializing_test(*this, &KeysCmdTest::_test_scan, _redis); +} + +template +void KeysCmdTest::_test_key() { + auto key = test_key("key"); + auto dest = test_key("dest"); + auto new_key_name = test_key("new-key"); + auto not_exist_key = test_key("not-exist"); + + KeyDeleter deleter(_redis, {key, dest, new_key_name}); + + REDIS_ASSERT(_redis.exists(key) == 0, "failed to test exists"); + + auto val = std::string("val"); + _redis.set(key, val); + + REDIS_ASSERT(_redis.exists({key, not_exist_key}) == 1, "failed to test exists"); + + auto new_val = _redis.dump(key); + REDIS_ASSERT(bool(new_val), "failed to test dump"); + + _redis.restore(dest, *new_val, std::chrono::seconds(1000)); + + new_val = _redis.get(dest); + REDIS_ASSERT(bool(new_val) && *new_val == val, "failed to test dump and restore"); + + _redis.rename(dest, new_key_name); + + bool not_exist = false; + try { + _redis.rename(not_exist_key, new_key_name); + } catch (const Error &e) { + not_exist = true; + } + REDIS_ASSERT(not_exist, "failed to test rename with nonexistent key"); + + REDIS_ASSERT(_redis.renamenx(new_key_name, dest), "failed to test renamenx"); + + REDIS_ASSERT(_redis.touch(not_exist_key) == 0, "failed to test touch"); + REDIS_ASSERT(_redis.touch({key, dest, new_key_name}) == 2, "failed to test touch"); + + REDIS_ASSERT(_redis.type(key) == "string", "failed to test type"); + + REDIS_ASSERT(_redis.del({new_key_name, dest}) == 1, "failed to test del"); + REDIS_ASSERT(_redis.unlink({new_key_name, key}) == 1, "failed to test unlink"); +} + +template +void KeysCmdTest::_test_randomkey(Redis &instance) { + auto key = test_key("randomkey"); + + KeyDeleter deleter(instance, key); + + instance.set(key, "value"); + + auto rand_key = instance.randomkey(); + REDIS_ASSERT(bool(rand_key), "failed to test randomkey"); +} + +template +void KeysCmdTest::_test_ttl() { + using namespace std::chrono; + + auto key = test_key("ttl"); + + KeyDeleter deleter(_redis, key); + + _redis.set(key, "val", seconds(100)); + auto ttl = _redis.ttl(key); + REDIS_ASSERT(ttl > 0 && ttl <= 100, "failed to test ttl"); + + REDIS_ASSERT(_redis.persist(key), "failed to test persist"); + ttl = _redis.ttl(key); + REDIS_ASSERT(ttl == -1, "failed to test ttl"); + + REDIS_ASSERT(_redis.expire(key, seconds(100)), + "failed to test expire"); + + auto tp = time_point_cast(system_clock::now() + seconds(100)); + REDIS_ASSERT(_redis.expireat(key, tp), "failed to test expireat"); + ttl = _redis.ttl(key); + REDIS_ASSERT(ttl > 0, "failed to test expireat"); + + REDIS_ASSERT(_redis.pexpire(key, milliseconds(100000)), "failed to test expire"); + + auto pttl = _redis.pttl(key); + REDIS_ASSERT(pttl > 0 && pttl <= 100000, "failed to test pttl"); + + auto tp_milli = time_point_cast(system_clock::now() + milliseconds(100000)); + REDIS_ASSERT(_redis.pexpireat(key, tp_milli), "failed to test pexpireat"); + pttl = _redis.pttl(key); + REDIS_ASSERT(pttl > 0, "failed to test pexpireat"); +} + +template +void KeysCmdTest::_test_scan(Redis &instance) { + std::string key_pattern = "!@#$%^&()_+alseufoawhnlkszd"; + auto k1 = test_key(key_pattern + "k1"); + auto k2 = test_key(key_pattern + "k2"); + auto k3 = test_key(key_pattern + "k3"); + + auto keys = {k1, k2, k3}; + + KeyDeleter deleter(instance, keys); + + instance.set(k1, "v"); + instance.set(k2, "v"); + instance.set(k3, "v"); + + auto cursor = 0; + std::unordered_set res; + while (true) { + cursor = instance.scan(cursor, "*" + key_pattern + "*", 2, std::inserter(res, res.end())); + if (cursor == 0) { + break; + } + } + REDIS_ASSERT(res == std::unordered_set(keys), + "failed to test scan"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.h new file mode 100644 index 000000000..2092fe9e4 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.h @@ -0,0 +1,55 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class ListCmdTest { +public: + explicit ListCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_lpoppush(); + + void _test_rpoppush(); + + void _test_list(); + + void _test_blocking(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "list_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.hpp new file mode 100644 index 000000000..fd26d8fde --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.hpp @@ -0,0 +1,154 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_HPP + +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void ListCmdTest::run() { + _test_lpoppush(); + + _test_rpoppush(); + + _test_list(); + + _test_blocking(); +} + +template +void ListCmdTest::_test_lpoppush() { + auto key = test_key("lpoppush"); + + KeyDeleter deleter(_redis, key); + + auto item = _redis.lpop(key); + REDIS_ASSERT(!item, "failed to test lpop"); + + REDIS_ASSERT(_redis.lpushx(key, "1") == 0, "failed to test lpushx"); + REDIS_ASSERT(_redis.lpush(key, "1") == 1, "failed to test lpush"); + REDIS_ASSERT(_redis.lpushx(key, "2") == 2, "failed to test lpushx"); + REDIS_ASSERT(_redis.lpush(key, {"3", "4", "5"}) == 5, "failed to test lpush"); + + item = _redis.lpop(key); + REDIS_ASSERT(item && *item == "5", "failed to test lpop"); +} + +template +void ListCmdTest::_test_rpoppush() { + auto key = test_key("rpoppush"); + + KeyDeleter deleter(_redis, key); + + auto item = _redis.rpop(key); + REDIS_ASSERT(!item, "failed to test rpop"); + + REDIS_ASSERT(_redis.rpushx(key, "1") == 0, "failed to test rpushx"); + REDIS_ASSERT(_redis.rpush(key, "1") == 1, "failed to test rpush"); + REDIS_ASSERT(_redis.rpushx(key, "2") == 2, "failed to test rpushx"); + REDIS_ASSERT(_redis.rpush(key, {"3", "4", "5"}) == 5, "failed to test rpush"); + + item = _redis.rpop(key); + REDIS_ASSERT(item && *item == "5", "failed to test rpop"); +} + +template +void ListCmdTest::_test_list() { + auto key = test_key("list"); + + KeyDeleter deleter(_redis, key); + + auto item = _redis.lindex(key, 0); + REDIS_ASSERT(!item, "failed to test lindex"); + + _redis.lpush(key, {"1", "2", "3", "4", "5"}); + + REDIS_ASSERT(_redis.lrem(key, 0, "3") == 1, "failed to test lrem"); + + REDIS_ASSERT(_redis.linsert(key, InsertPosition::BEFORE, "2", "3") == 5, + "failed to test lindex"); + + REDIS_ASSERT(_redis.llen(key) == 5, "failed to test llen"); + + _redis.lset(key, 0, "6"); + item = _redis.lindex(key, 0); + REDIS_ASSERT(item && *item == "6", "failed to test lindex"); + + _redis.ltrim(key, 0, 2); + + std::vector res; + _redis.lrange(key, 0, -1, std::back_inserter(res)); + REDIS_ASSERT(res == std::vector({"6", "4", "3"}), "failed to test ltrim"); +} + +template +void ListCmdTest::_test_blocking() { + auto k1 = test_key("k1"); + auto k2 = test_key("k2"); + auto k3 = test_key("k3"); + + auto keys = {k1, k2, k3}; + + KeyDeleter deleter(_redis, keys); + + std::string val("value"); + _redis.lpush(k1, val); + + auto res = _redis.blpop(keys.begin(), keys.end()); + REDIS_ASSERT(res && *res == std::make_pair(k1, val), "failed to test blpop"); + + res = _redis.brpop(keys, std::chrono::seconds(1)); + REDIS_ASSERT(!res, "failed to test brpop with timeout"); + + _redis.lpush(k1, val); + res = _redis.blpop(k1); + REDIS_ASSERT(res && *res == std::make_pair(k1, val), "failed to test blpop"); + + res = _redis.blpop(k1, std::chrono::seconds(1)); + REDIS_ASSERT(!res, "failed to test blpop with timeout"); + + _redis.lpush(k1, val); + res = _redis.brpop(k1); + REDIS_ASSERT(res && *res == std::make_pair(k1, val), "failed to test brpop"); + + res = _redis.brpop(k1, std::chrono::seconds(1)); + REDIS_ASSERT(!res, "failed to test brpop with timeout"); + + auto str = _redis.brpoplpush(k2, k3, std::chrono::seconds(1)); + REDIS_ASSERT(!str, "failed to test brpoplpush with timeout"); + + _redis.lpush(k2, val); + str = _redis.brpoplpush(k2, k3); + REDIS_ASSERT(str && *str == val, "failed to test brpoplpush"); + + str = _redis.rpoplpush(k3, k2); + REDIS_ASSERT(str && *str == val, "failed to test rpoplpush"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.h new file mode 100644 index 000000000..fc6b80dde --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.h @@ -0,0 +1,57 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class PipelineTransactionTest { +public: + explicit PipelineTransactionTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + Pipeline _pipeline(const StringView &key); + + Transaction _transaction(const StringView &key, bool piped); + + void _test_pipeline(const StringView &key, Pipeline &pipe); + + void _test_transaction(const StringView &key, Transaction &tx); + + void _test_watch(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "pipeline_transaction_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.hpp new file mode 100644 index 000000000..4e6a745cb --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.hpp @@ -0,0 +1,184 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_HPP + +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void PipelineTransactionTest::run() { + { + auto key = test_key("pipeline"); + KeyDeleter deleter(_redis, key); + auto pipe = _pipeline(key); + _test_pipeline(key, pipe); + } + + { + auto key = test_key("transaction"); + KeyDeleter deleter(_redis, key); + auto tx = _transaction(key, true); + _test_transaction(key, tx); + } + + { + auto key = test_key("transaction"); + KeyDeleter deleter(_redis, key); + auto tx = _transaction(key, false); + _test_transaction(key, tx); + } + + _test_watch(); +} + +template +Pipeline PipelineTransactionTest::_pipeline(const StringView &) { + return _redis.pipeline(); +} + +template <> +inline Pipeline PipelineTransactionTest::_pipeline(const StringView &key) { + return _redis.pipeline(key); +} + +template +Transaction PipelineTransactionTest::_transaction(const StringView &, bool piped) { + return _redis.transaction(piped); +} + +template <> +inline Transaction PipelineTransactionTest::_transaction(const StringView &key, + bool piped) { + return _redis.transaction(key, piped); +} + +template +void PipelineTransactionTest::_test_pipeline(const StringView &key, + Pipeline &pipe) { + std::string val("value"); + auto replies = pipe.set(key, val) + .get(key) + .strlen(key) + .exec(); + + REDIS_ASSERT(replies.get(0), "failed to test pipeline with set operation"); + + auto new_val = replies.get(1); + std::size_t len = replies.get(2); + REDIS_ASSERT(bool(new_val) && *new_val == val && len == val.size(), + "failed to test pipeline with string operations"); + + REDIS_ASSERT(reply::parse(replies.get(0)), "failed to test pipeline with set operation"); + + new_val = reply::parse(replies.get(1)); + len = reply::parse(replies.get(2)); + REDIS_ASSERT(bool(new_val) && *new_val == val && len == val.size(), + "failed to test pipeline with string operations"); +} + +template +void PipelineTransactionTest::_test_transaction(const StringView &key, + Transaction &tx) { + std::unordered_map m = { + std::make_pair("f1", "v1"), + std::make_pair("f2", "v2"), + std::make_pair("f3", "v3") + }; + auto replies = tx.hmset(key, m.begin(), m.end()) + .hgetall(key) + .hdel(key, "f1") + .exec(); + + replies.get(0); + + decltype(m) mm; + replies.get(1, std::inserter(mm, mm.end())); + REDIS_ASSERT(mm == m, "failed to test transaction"); + + REDIS_ASSERT(replies.get(2) == 1, "failed to test transaction"); + + tx.set(key, "value") + .get(key) + .incr(key); + + tx.discard(); + + replies = tx.del(key) + .set(key, "value") + .exec(); + + REDIS_ASSERT(replies.get(0) == 1, "failed to test transaction"); + + REDIS_ASSERT(replies.get(1), "failed to test transaction"); +} + +template +void PipelineTransactionTest::_test_watch() { + auto key = test_key("watch"); + + KeyDeleter deleter(_redis, key); + + { + auto tx = _transaction(key, false); + + auto redis = tx.redis(); + + redis.watch(key); + + auto replies = tx.set(key, "1").get(key).exec(); + + REDIS_ASSERT(replies.size() == 2 + && replies.template get(0) == true, "failed to test watch"); + + auto val = replies.template get(1); + + REDIS_ASSERT(val && *val == "1", "failed to test watch"); + } + + try { + auto tx = _transaction(key, false); + + auto redis = tx.redis(); + + redis.watch(key); + + // Key has been modified by other client. + _redis.set(key, "val"); + + // Transaction should fail, and throw WatchError + tx.set(key, "1").exec(); + + REDIS_ASSERT(false, "failed to test watch"); + } catch (const sw::redis::WatchError &err) { + // Catch the error. + } +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.h new file mode 100644 index 000000000..f816eaecc --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.h @@ -0,0 +1,53 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SUBPUB_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_SUBPUB_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class PubSubTest { +public: + explicit PubSubTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_sub_channel(); + + void _test_sub_pattern(); + + void _test_unsubscribe(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "pubsub_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SUBPUB_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.hpp new file mode 100644 index 000000000..4530c0788 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.hpp @@ -0,0 +1,244 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SUBPUB_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_SUBPUB_TEST_HPP + +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void PubSubTest::run() { + _test_sub_channel(); + + _test_sub_pattern(); + + _test_unsubscribe(); +} + +template +void PubSubTest::_test_sub_channel() { + auto sub = _redis.subscriber(); + + auto msgs = {"msg1", "msg2"}; + auto channel1 = test_key("c1"); + sub.on_message([&msgs, &channel1](std::string channel, std::string msg) { + static std::size_t idx = 0; + REDIS_ASSERT(channel == channel1 && msg == *(msgs.begin() + idx), + "failed to test subscribe"); + ++idx; + }); + + sub.subscribe(channel1); + + // Consume the SUBSCRIBE message. + sub.consume(); + + for (const auto &msg : msgs) { + _redis.publish(channel1, msg); + sub.consume(); + } + + sub.unsubscribe(channel1); + + // Consume the UNSUBSCRIBE message. + sub.consume(); + + auto channel2 = test_key("c2"); + auto channel3 = test_key("c3"); + auto channel4 = test_key("c4"); + std::unordered_set channels; + sub.on_meta([&channels](Subscriber::MsgType type, + OptionalString channel, + long long num) { + REDIS_ASSERT(bool(channel), "failed to test subscribe"); + + if (type == Subscriber::MsgType::SUBSCRIBE) { + auto iter = channels.find(*channel); + REDIS_ASSERT(iter == channels.end(), "failed to test subscribe"); + channels.insert(*channel); + REDIS_ASSERT(static_cast(num) == channels.size(), + "failed to test subscribe"); + } else if (type == Subscriber::MsgType::UNSUBSCRIBE) { + auto iter = channels.find(*channel); + REDIS_ASSERT(iter != channels.end(), "failed to test subscribe"); + channels.erase(*channel); + REDIS_ASSERT(static_cast(num) == channels.size(), + "failed to test subscribe"); + } else { + REDIS_ASSERT(false, "Unknown message type"); + } + }); + + std::unordered_map messages = { + {channel2, "msg2"}, + {channel3, "msg3"}, + {channel4, "msg4"}, + }; + sub.on_message([&messages](std::string channel, std::string msg) { + REDIS_ASSERT(messages.find(channel) != messages.end(), + "failed to test subscribe"); + REDIS_ASSERT(messages[channel] == msg, "failed to test subscribe"); + }); + + sub.subscribe({channel2, channel3, channel4}); + + for (std::size_t idx = 0; idx != channels.size(); ++idx) { + sub.consume(); + } + + for (const auto &ele : messages) { + _redis.publish(ele.first, ele.second); + sub.consume(); + } + + auto tmp = {channel2, channel3, channel4}; + sub.unsubscribe(tmp); + + for (std::size_t idx = 0; idx != tmp.size(); ++idx) { + sub.consume(); + } +} + +template +void PubSubTest::_test_sub_pattern() { + auto sub = _redis.subscriber(); + + auto msgs = {"msg1", "msg2"}; + auto pattern1 = test_key("pattern*"); + std::string channel1 = test_key("pattern1"); + sub.on_pmessage([&msgs, &pattern1, &channel1](std::string pattern, + std::string channel, + std::string msg) { + static std::size_t idx = 0; + REDIS_ASSERT(pattern == pattern1 + && channel == channel1 + && msg == *(msgs.begin() + idx), + "failed to test psubscribe"); + ++idx; + }); + + sub.psubscribe(pattern1); + + // Consume the PSUBSCRIBE message. + sub.consume(); + + for (const auto &msg : msgs) { + _redis.publish(channel1, msg); + sub.consume(); + } + + sub.punsubscribe(pattern1); + + // Consume the PUNSUBSCRIBE message. + sub.consume(); + + auto channel2 = test_key("pattern22"); + auto channel3 = test_key("pattern33"); + auto channel4 = test_key("pattern44"); + std::unordered_set channels; + sub.on_meta([&channels](Subscriber::MsgType type, + OptionalString channel, + long long num) { + REDIS_ASSERT(bool(channel), "failed to test psubscribe"); + + if (type == Subscriber::MsgType::PSUBSCRIBE) { + auto iter = channels.find(*channel); + REDIS_ASSERT(iter == channels.end(), "failed to test psubscribe"); + channels.insert(*channel); + REDIS_ASSERT(static_cast(num) == channels.size(), + "failed to test psubscribe"); + } else if (type == Subscriber::MsgType::PUNSUBSCRIBE) { + auto iter = channels.find(*channel); + REDIS_ASSERT(iter != channels.end(), "failed to test psubscribe"); + channels.erase(*channel); + REDIS_ASSERT(static_cast(num) == channels.size(), + "failed to test psubscribe"); + } else { + REDIS_ASSERT(false, "Unknown message type"); + } + }); + + auto pattern2 = test_key("pattern2*"); + auto pattern3 = test_key("pattern3*"); + auto pattern4 = test_key("pattern4*"); + std::unordered_set patterns = {pattern2, pattern3, pattern4}; + + std::unordered_map messages = { + {channel2, "msg2"}, + {channel3, "msg3"}, + {channel4, "msg4"}, + }; + sub.on_pmessage([&patterns, &messages](std::string pattern, + std::string channel, + std::string msg) { + REDIS_ASSERT(patterns.find(pattern) != patterns.end(), + "failed to test psubscribe"); + REDIS_ASSERT(messages[channel] == msg, "failed to test psubscribe"); + }); + + sub.psubscribe({pattern2, pattern3, pattern4}); + + for (std::size_t idx = 0; idx != channels.size(); ++idx) { + sub.consume(); + } + + for (const auto &ele : messages) { + _redis.publish(ele.first, ele.second); + sub.consume(); + } + + auto tmp = {pattern2, pattern3, pattern4}; + sub.punsubscribe(tmp); + + for (std::size_t idx = 0; idx != tmp.size(); ++idx) { + sub.consume(); + } +} + +template +void PubSubTest::_test_unsubscribe() { + auto sub = _redis.subscriber(); + + sub.on_meta([](Subscriber::MsgType type, + OptionalString channel, + long long num) { + REDIS_ASSERT(type == Subscriber::MsgType::UNSUBSCRIBE, + "failed to test unsub"); + + REDIS_ASSERT(!channel, "failed to test unsub"); + + REDIS_ASSERT(num == 0, "failed to test unsub"); + }); + + sub.unsubscribe(); + sub.consume(); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SUBPUB_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.h new file mode 100644 index 000000000..e41fdef9d --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.h @@ -0,0 +1,76 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SANITY_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_SANITY_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class SanityTest { +public: + SanityTest(const ConnectionOptions &opts, RedisInstance &instance) + : _opts(opts), _redis(instance) {} + + void run(); + +private: + void _test_uri_ctor(); + + void _ping(Redis &instance); + + void _test_move_ctor(); + + void _test_cmdargs(); + + void _test_generic_command(); + + void _test_hash_tag(); + + void _test_hash_tag(std::initializer_list keys); + + std::string _test_key(const std::string &key); + + void _test_ping(Redis &instance); + + void _test_pipeline(const StringView &key, Pipeline &pipeline); + + void _test_transaction(const StringView &key, Transaction &transaction); + + Pipeline _pipeline(const StringView &key); + + Transaction _transaction(const StringView &key); + + ConnectionOptions _opts; + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "sanity_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SANITY_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.hpp new file mode 100644 index 000000000..cff992ecb --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.hpp @@ -0,0 +1,299 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SANITY_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_SANITY_TEST_HPP + +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void SanityTest::run() { + _test_uri_ctor(); + + _test_move_ctor(); + + cluster_specializing_test(*this, &SanityTest::_test_ping, _redis); + + auto pipe_key = test_key("pipeline"); + auto tx_key = test_key("transaction"); + + KeyDeleter deleter(_redis, {pipe_key, tx_key}); + + auto pipeline = _pipeline(pipe_key); + _test_pipeline(pipe_key, pipeline); + + auto transaction = _transaction(tx_key); + _test_transaction(tx_key, transaction); + + _test_cmdargs(); + + _test_generic_command(); +} + +template +void SanityTest::_test_uri_ctor() { + std::string uri; + switch (_opts.type) { + case sw::redis::ConnectionType::TCP: + uri = "tcp://" + _opts.host + ":" + std::to_string(_opts.port); + break; + + case sw::redis::ConnectionType::UNIX: + REDIS_ASSERT(false, "NO test for UNIX Domain Socket"); + break; + + default: + REDIS_ASSERT(false, "Unknown connection type"); + } + + auto instance = RedisInstance(uri); + + cluster_specializing_test(*this, &SanityTest::_ping, instance); +} + +template +void SanityTest::_ping(Redis &instance) { + try { + auto pong = instance.ping(); + REDIS_ASSERT(pong == "PONG", "Failed to test constructing Redis with uri"); + } catch (const sw::redis::ReplyError &e) { + REDIS_ASSERT(e.what() == std::string("NOAUTH Authentication required."), + "Failed to test constructing Redis with uri"); + } +} + +template +void SanityTest::_test_move_ctor() { + auto test_move_ctor = std::move(_redis); + + _redis = std::move(test_move_ctor); +} + +template +void SanityTest::_test_cmdargs() { + auto lpush_num = [](Connection &connection, const StringView &key, long long num) { + connection.send("LPUSH %b %lld", + key.data(), key.size(), + num); + }; + + auto lpush_nums = [](Connection &connection, + const StringView &key, + const std::vector &nums) { + CmdArgs args; + args.append("LPUSH").append(key); + for (auto num : nums) { + args.append(std::to_string(num)); + } + + connection.send(args); + }; + + auto key = test_key("lpush_num"); + + KeyDeleter deleter(_redis, key); + + auto reply = _redis.command(lpush_num, key, 1); + REDIS_ASSERT(reply::parse(*reply) == 1, "failed to test cmdargs"); + + std::vector nums = {2, 3, 4, 5}; + reply = _redis.command(lpush_nums, key, nums); + REDIS_ASSERT(reply::parse(*reply) == 5, "failed to test cmdargs"); + + std::vector res; + _redis.lrange(key, 0, -1, std::back_inserter(res)); + REDIS_ASSERT((res == std::vector{"5", "4", "3", "2", "1"}), + "failed to test cmdargs"); +} + +template +void SanityTest::_test_generic_command() { + auto key = test_key("key"); + auto not_exist_key = test_key("not_exist_key"); + auto k1 = test_key("k1"); + auto k2 = test_key("k2"); + + KeyDeleter deleter(_redis, {key, not_exist_key, k1, k2}); + + std::string cmd("set"); + _redis.command(cmd, key, 123); + auto reply = _redis.command("get", key); + auto val = reply::parse(*reply); + REDIS_ASSERT(val && *val == "123", "failed to test generic command"); + + val = _redis.template command("get", key); + REDIS_ASSERT(val && *val == "123", "failed to test generic command"); + + std::vector res; + _redis.command("mget", key, not_exist_key, std::back_inserter(res)); + REDIS_ASSERT(res.size() == 2 && res[0] && *res[0] == "123" && !res[1], + "failed to test generic command"); + + reply = _redis.command("incr", key); + REDIS_ASSERT(reply::parse(*reply) == 124, "failed to test generic command"); + + _redis.command("mset", k1.c_str(), "v", k2.c_str(), "v"); + reply = _redis.command("mget", k1, k2); + res.clear(); + reply::to_array(*reply, std::back_inserter(res)); + REDIS_ASSERT(res.size() == 2 && res[0] && *(res[0]) == "v" && res[1] && *(res[1]) == "v", + "failed to test generic command"); + + res = _redis.template command>("mget", k1, k2); + REDIS_ASSERT(res.size() == 2 && res[0] && *(res[0]) == "v" && res[1] && *(res[1]) == "v", + "failed to test generic command"); + + res.clear(); + _redis.command("mget", k1, k2, std::back_inserter(res)); + REDIS_ASSERT(res.size() == 2 && res[0] && *(res[0]) == "v" && res[1] && *(res[1]) == "v", + "failed to test generic command"); + + auto set_cmd_str = {"set", key.c_str(), "new_value"}; + _redis.command(set_cmd_str.begin(), set_cmd_str.end()); + + auto get_cmd_str = {"get", key.c_str()}; + reply = _redis.command(get_cmd_str.begin(), get_cmd_str.end()); + val = reply::parse(*reply); + REDIS_ASSERT(val && *val == "new_value", "failed to test generic command"); + + val = _redis.template command(get_cmd_str.begin(), get_cmd_str.end()); + REDIS_ASSERT(val && *val == "new_value", "failed to test generic command"); + + auto mget_cmd_str = {"mget", key.c_str(), not_exist_key.c_str()}; + res.clear(); + _redis.command(mget_cmd_str.begin(), mget_cmd_str.end(), std::back_inserter(res)); + REDIS_ASSERT(res.size() == 2 && res[0] && *res[0] == "new_value" && !res[1], + "failed to test generic command"); +} + +template +void SanityTest::_test_hash_tag() { + _test_hash_tag({_test_key("{tag}postfix1"), + _test_key("{tag}postfix2"), + _test_key("{tag}postfix3")}); + + _test_hash_tag({_test_key("prefix1{tag}postfix1"), + _test_key("prefix2{tag}postfix2"), + _test_key("prefix3{tag}postfix3")}); + + _test_hash_tag({_test_key("prefix1{tag}"), + _test_key("prefix2{tag}"), + _test_key("prefix3{tag}")}); + + _test_hash_tag({_test_key("prefix{}postfix"), + _test_key("prefix{}postfix"), + _test_key("prefix{}postfix")}); + + _test_hash_tag({_test_key("prefix1{tag}post}fix1"), + _test_key("prefix2{tag}pos}tfix2"), + _test_key("prefix3{tag}postfi}x3")}); + + _test_hash_tag({_test_key("prefix1{t{ag}postfix1"), + _test_key("prefix2{t{ag}postfix2"), + _test_key("prefix3{t{ag}postfix3")}); + + _test_hash_tag({_test_key("prefix1{t{ag}postfi}x1"), + _test_key("prefix2{t{ag}post}fix2"), + _test_key("prefix3{t{ag}po}stfix3")}); +} + +template +void SanityTest::_test_hash_tag(std::initializer_list keys) { + KeyDeleter deleter(_redis, keys.begin(), keys.end()); + + std::string value = "value"; + std::vector> kvs; + for (const auto &key : keys) { + kvs.emplace_back(key, value); + } + + _redis.mset(kvs.begin(), kvs.end()); + + std::vector res; + res.reserve(keys.size()); + _redis.mget(keys.begin(), keys.end(), std::back_inserter(res)); + + REDIS_ASSERT(res.size() == keys.size(), "failed to test hash tag"); + + for (const auto &ele : res) { + REDIS_ASSERT(ele && *ele == value, "failed to test hash tag"); + } +} + +template +std::string SanityTest::_test_key(const std::string &key) { + REDIS_ASSERT(key.size() > 1, "failed to generate key"); + + // Ensure that key prefix has NO hash tag. Also see the implementation of test_key. + return key.substr(1); +} + +template +void SanityTest::_test_ping(Redis &instance) { + auto reply = instance.command("ping"); + REDIS_ASSERT(reply && reply::parse(*reply) == "PONG", + "failed to test generic command"); + + auto pong = instance.command("ping"); + REDIS_ASSERT(pong == "PONG", "failed to test generic command"); +} + +template +void SanityTest::_test_pipeline(const StringView &key, Pipeline &pipeline) { + auto pipe_replies = pipeline.command("set", key, "value").command("get", key).exec(); + auto val = pipe_replies.get(1); + REDIS_ASSERT(val && *val == "value", "failed to test generic command"); +} + +template +void SanityTest::_test_transaction(const StringView &key, Transaction &transaction) { + auto tx_replies = transaction.command("set", key, 456).command("incr", key).exec(); + REDIS_ASSERT(tx_replies.get(1) == 457, "failed to test generic command"); +} + +template +Pipeline SanityTest::_pipeline(const StringView &) { + return _redis.pipeline(); +} + +template <> +inline Pipeline SanityTest::_pipeline(const StringView &key) { + return _redis.pipeline(key); +} + +template +Transaction SanityTest::_transaction(const StringView &) { + return _redis.transaction(); +} + +template <> +inline Transaction SanityTest::_transaction(const StringView &key) { + return _redis.transaction(key); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SANITY_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.h new file mode 100644 index 000000000..f7adb11b6 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.h @@ -0,0 +1,49 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SCRIPT_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_SCRIPT_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class ScriptCmdTest { +public: + explicit ScriptCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _run(Redis &instance); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "script_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SCRIPT_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.hpp new file mode 100644 index 000000000..76e07fda2 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.hpp @@ -0,0 +1,97 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SCRIPT_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_SCRIPT_CMDS_TEST_HPP + +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void ScriptCmdTest::run() { + cluster_specializing_test(*this, + &ScriptCmdTest::_run, + _redis); +} + +template +void ScriptCmdTest::_run(Redis &instance) { + auto key1 = test_key("k1"); + auto key2 = test_key("k2"); + + KeyDeleter deleter(instance, {key1, key2}); + + std::string script = "redis.call('set', KEYS[1], 1);" + "redis.call('set', KEYS[2], 2);" + "local first = redis.call('get', KEYS[1]);" + "local second = redis.call('get', KEYS[2]);" + "return first + second"; + + auto num = instance.eval(script, {key1, key2}, {}); + REDIS_ASSERT(num == 3, "failed to test scripting for cluster"); + + script = "return 1"; + num = instance.eval(script, {}, {}); + REDIS_ASSERT(num == 1, "failed to test eval"); + + auto script_with_args = "return {ARGV[1] + 1, ARGV[2] + 2, ARGV[3] + 3}"; + std::vector res; + instance.eval(script_with_args, + {"k"}, + {"1", "2", "3"}, + std::back_inserter(res)); + REDIS_ASSERT(res == std::vector({2, 4, 6}), + "failed to test eval with array reply"); + + auto sha1 = instance.script_load(script); + num = instance.evalsha(sha1, {}, {}); + REDIS_ASSERT(num == 1, "failed to test evalsha"); + + auto sha2 = instance.script_load(script_with_args); + res.clear(); + instance.evalsha(sha2, + {"k"}, + {"1", "2", "3"}, + std::back_inserter(res)); + REDIS_ASSERT(res == std::vector({2, 4, 6}), + "failed to test evalsha with array reply"); + + std::list exist_res; + instance.script_exists({sha1, sha2, std::string("not exist")}, std::back_inserter(exist_res)); + REDIS_ASSERT(exist_res == std::list({true, true, false}), + "failed to test script exists"); + + instance.script_flush(); + exist_res.clear(); + instance.script_exists({sha1, sha2, std::string("not exist")}, std::back_inserter(exist_res)); + REDIS_ASSERT(exist_res == std::list({false, false, false}), + "failed to test script flush"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SCRIPT_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.h new file mode 100644 index 000000000..c5320d793 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.h @@ -0,0 +1,53 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SET_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_SET_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class SetCmdTest { +public: + explicit SetCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_set(); + + void _test_multi_set(); + + void _test_sscan(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "set_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SET_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.hpp new file mode 100644 index 000000000..1a4c24bfd --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.hpp @@ -0,0 +1,184 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SET_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_SET_CMDS_TEST_HPP + +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void SetCmdTest::run() { + _test_set(); + + _test_multi_set(); + + _test_sscan(); +} + +template +void SetCmdTest::_test_set() { + auto key = test_key("set"); + + KeyDeleter deleter(_redis, key); + + std::string m1("m1"); + std::string m2("m2"); + std::string m3("m3"); + + REDIS_ASSERT(_redis.sadd(key, m1) == 1, "failed to test sadd"); + + auto members = {m1, m2, m3}; + REDIS_ASSERT(_redis.sadd(key, members) == 2, "failed to test sadd with multiple members"); + + REDIS_ASSERT(_redis.scard(key) == 3, "failed to test scard"); + + REDIS_ASSERT(_redis.sismember(key, m1), "failed to test sismember"); + + std::unordered_set res; + _redis.smembers(key, std::inserter(res, res.end())); + REDIS_ASSERT(res.find(m1) != res.end() + && res.find(m2) != res.end() + && res.find(m3) != res.end(), + "failed to test smembers"); + + auto ele = _redis.srandmember(key); + REDIS_ASSERT(bool(ele) && res.find(*ele) != res.end(), "failed to test srandmember"); + + std::vector rand_members; + _redis.srandmember(key, 2, std::back_inserter(rand_members)); + REDIS_ASSERT(rand_members.size() == 2, "failed to test srandmember"); + + ele = _redis.spop(key); + REDIS_ASSERT(bool(ele) && res.find(*ele) != res.end(), "failed to test spop"); + + rand_members.clear(); + _redis.spop(key, 3, std::back_inserter(rand_members)); + REDIS_ASSERT(rand_members.size() == 2, "failed to test srandmember"); + + rand_members.clear(); + _redis.srandmember(key, 2, std::back_inserter(rand_members)); + REDIS_ASSERT(rand_members.empty(), "failed to test srandmember with empty set"); + + _redis.spop(key, 2, std::back_inserter(rand_members)); + REDIS_ASSERT(rand_members.empty(), "failed to test spop with empty set"); + + _redis.sadd(key, members); + REDIS_ASSERT(_redis.srem(key, m1) == 1, "failed to test srem"); + REDIS_ASSERT(_redis.srem(key, members) == 2, "failed to test srem with mulitple members"); + REDIS_ASSERT(_redis.srem(key, members) == 0, "failed to test srem with mulitple members"); +} + +template +void SetCmdTest::_test_multi_set() { + auto k1 = test_key("s1"); + auto k2 = test_key("s2"); + auto k3 = test_key("s3"); + auto k4 = test_key("s4"); + auto k5 = test_key("s5"); + auto k6 = test_key("s6"); + + KeyDeleter keys(_redis, {k1, k2, k3, k4, k5, k6}); + + _redis.sadd(k1, {"a", "c"}); + _redis.sadd(k2, {"a", "b"}); + std::vector sdiff; + _redis.sdiff({k1, k1}, std::back_inserter(sdiff)); + REDIS_ASSERT(sdiff.empty(), "failed to test sdiff"); + + _redis.sdiff({k1, k2}, std::back_inserter(sdiff)); + REDIS_ASSERT(sdiff == std::vector({"c"}), "failed to test sdiff"); + + _redis.sdiffstore(k3, {k1, k2}); + sdiff.clear(); + _redis.smembers(k3, std::back_inserter(sdiff)); + REDIS_ASSERT(sdiff == std::vector({"c"}), "failed to test sdiffstore"); + + REDIS_ASSERT(_redis.sdiffstore(k3, k1) == 2, "failed to test sdiffstore"); + + REDIS_ASSERT(_redis.sinterstore(k3, k1) == 2, "failed to test sinterstore"); + + REDIS_ASSERT(_redis.sunionstore(k3, k1) == 2, "failed to test sunionstore"); + + std::vector sinter; + _redis.sinter({k1, k2}, std::back_inserter(sinter)); + REDIS_ASSERT(sinter == std::vector({"a"}), "failed to test sinter"); + + _redis.sinterstore(k4, {k1, k2}); + sinter.clear(); + _redis.smembers(k4, std::back_inserter(sinter)); + REDIS_ASSERT(sinter == std::vector({"a"}), "failed to test sinterstore"); + + std::unordered_set sunion; + _redis.sunion({k1, k2}, std::inserter(sunion, sunion.end())); + REDIS_ASSERT(sunion == std::unordered_set({"a", "b", "c"}), + "failed to test sunion"); + + _redis.sunionstore(k5, {k1, k2}); + sunion.clear(); + _redis.smembers(k5, std::inserter(sunion, sunion.end())); + REDIS_ASSERT(sunion == std::unordered_set({"a", "b", "c"}), + "failed to test sunionstore"); + + REDIS_ASSERT(_redis.smove(k5, k6, "a"), "failed to test smove"); +} + +template +void SetCmdTest::_test_sscan() { + auto key = test_key("sscan"); + + KeyDeleter deleter(_redis, key); + + std::unordered_set members = {"m1", "m2", "m3"}; + _redis.sadd(key, members.begin(), members.end()); + + std::unordered_set res; + long long cursor = 0; + while (true) { + cursor = _redis.sscan(key, cursor, "m*", 1, std::inserter(res, res.end())); + if (cursor == 0) { + break; + } + } + + REDIS_ASSERT(res == members, "failed to test sscan"); + + res.clear(); + cursor = 0; + while (true) { + cursor = _redis.sscan(key, cursor, std::inserter(res, res.end())); + if (cursor == 0) { + break; + } + } + + REDIS_ASSERT(res == members, "failed to test sscan"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SET_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.h new file mode 100644 index 000000000..24873a8b4 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.h @@ -0,0 +1,54 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_STREAM_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_STREAM_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class StreamCmdsTest { +public: + explicit StreamCmdsTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + using Item = std::pair>; + using Result = std::unordered_map>; + + void _test_stream_cmds(); + + void _test_group_cmds(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "stream_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_STREAM_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.hpp new file mode 100644 index 000000000..df6f57690 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.hpp @@ -0,0 +1,225 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_STREAM_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_STREAM_CMDS_TEST_HPP + +#include +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void StreamCmdsTest::run() { + _test_stream_cmds(); + + _test_group_cmds(); +} + +template +void StreamCmdsTest::_test_stream_cmds() { + auto key = test_key("stream"); + + KeyDeleter deleter(_redis, key); + + std::vector> attrs = { + {"f1", "v1"}, + {"f2", "v2"} + }; + auto id = "1565427842-0"; + REDIS_ASSERT(_redis.xadd(key, id, attrs.begin(), attrs.end()) == id, + "failed to test xadd"); + + std::vector> keys = {std::make_pair(key, "0-0")}; + Result result; + _redis.xread(keys.begin(), keys.end(), 1, std::inserter(result, result.end())); + + REDIS_ASSERT(result.size() == 1 + && result.find(key) != result.end() + && result[key].size() == 1 + && result[key][0].first == id + && result[key][0].second.size() == 2, + "failed to test xread"); + + result.clear(); + _redis.xread(key, std::string("0-0"), 1, std::inserter(result, result.end())); + + REDIS_ASSERT(result.size() == 1 + && result.find(key) != result.end() + && result[key].size() == 1 + && result[key][0].first == id + && result[key][0].second.size() == 2, + "failed to test xread"); + + result.clear(); + keys = {std::make_pair(key, id)}; + _redis.xread(keys.begin(), + keys.end(), + std::chrono::seconds(1), + 2, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 0, "failed to test xread"); + + _redis.xread(key, + id, + std::chrono::seconds(1), + 2, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 0, "failed to test xread"); + + id = "1565427842-1"; + REDIS_ASSERT(_redis.xadd(key, id, attrs.begin(), attrs.end()) == id, + "failed to test xadd"); + + REDIS_ASSERT(_redis.xlen(key) == 2, "failed to test xlen"); + + REDIS_ASSERT(_redis.xtrim(key, 1, false) == 1, "failed to test xtrim"); + + std::vector items; + _redis.xrange(key, "-", "+", std::back_inserter(items)); + REDIS_ASSERT(items.size() == 1 && items[0].first == id, "failed to test xrange"); + + items.clear(); + _redis.xrevrange(key, "+", "-", std::back_inserter(items)); + REDIS_ASSERT(items.size() == 1 && items[0].first == id, "failed to test xrevrange"); + + REDIS_ASSERT(_redis.xdel(key, {id, "111-111"}) == 1, "failed to test xdel"); +} + +template +void StreamCmdsTest::_test_group_cmds() { + auto key = test_key("stream"); + + KeyDeleter deleter(_redis, key); + + auto group = "group"; + auto consumer1 = "consumer1"; + + _redis.xgroup_create(key, group, "$", true); + + std::vector> attrs = { + {"f1", "v1"}, + {"f2", "v2"} + }; + auto id = _redis.xadd(key, "*", attrs.begin(), attrs.end()); + auto keys = {std::make_pair(key, ">")}; + + Result result; + _redis.xreadgroup(group, + consumer1, + keys.begin(), + keys.end(), + 1, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 1 + && result.find(key) != result.end() + && result[key].size() == 1 + && result[key][0].first == id, + "failed to test xreadgroup"); + + result.clear(); + _redis.xreadgroup(group, + consumer1, + key, + std::string(">"), + 1, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 0, "failed to test xreadgroup"); + + result.clear(); + _redis.xreadgroup(group, + "not-exist-consumer", + keys.begin(), + keys.end(), + 1, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 0, "failed to test xreadgroup"); + + result.clear(); + _redis.xreadgroup(group, + consumer1, + keys.begin(), + keys.end(), + std::chrono::seconds(1), + 1, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 0, "failed to test xreadgroup"); + + result.clear(); + _redis.xreadgroup(group, + consumer1, + key, + ">", + std::chrono::seconds(1), + 1, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 0, "failed to test xreadgroup"); + + using PendingResult = std::vector>; + PendingResult pending_result; + _redis.xpending(key, group, "-", "+", 1, consumer1, std::back_inserter(pending_result)); + + REDIS_ASSERT(pending_result.size() == 1 + && std::get<0>(pending_result[0]) == id + && std::get<1>(pending_result[0]) == consumer1, + "failed to test xpending"); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + auto consumer2 = "consumer2"; + std::vector items; + auto ids = {id}; + _redis.xclaim(key, + group, + consumer2, + std::chrono::milliseconds(10), + ids, + std::back_inserter(items)); + REDIS_ASSERT(items.size() == 1 && items[0].first == id, "failed to test xclaim"); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + items.clear(); + _redis.xclaim(key, group, consumer1, std::chrono::milliseconds(10), id, std::back_inserter(items)); + REDIS_ASSERT(items.size() == 1 && items[0].first == id, "failed to test xclaim: " + std::to_string(items.size())); + + _redis.xack(key, group, id); + + REDIS_ASSERT(_redis.xgroup_delconsumer(key, group, consumer1) == 0, + "failed to test xgroup_delconsumer"); + + REDIS_ASSERT(_redis.xgroup_delconsumer(key, group, consumer2) == 0, + "failed to test xgroup_delconsumer"); + + REDIS_ASSERT(_redis.xgroup_destroy(key, group) == 1, + "failed to test xgroup_destroy"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_STREAM_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.h new file mode 100644 index 000000000..86788b2bc --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.h @@ -0,0 +1,57 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_STRING_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_STRING_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class StringCmdTest { +public: + explicit StringCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_str(); + + void _test_bit(); + + void _test_numeric(); + + void _test_getset(); + + void _test_mgetset(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "string_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_STRING_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.hpp new file mode 100644 index 000000000..b8a0aa3c1 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.hpp @@ -0,0 +1,247 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_STRING_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_STRING_CMDS_TEST_HPP + +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void StringCmdTest::run() { + _test_str(); + + _test_bit(); + + _test_numeric(); + + _test_getset(); + + _test_mgetset(); +} + +template +void StringCmdTest::_test_str() { + auto key = test_key("str"); + + KeyDeleter deleter(_redis, key); + + std::string val("value"); + + long long val_size = val.size(); + + auto len1 = _redis.append(key, val); + REDIS_ASSERT(len1 == val_size, "failed to append to non-existent key"); + + auto len2 = _redis.append(key, val); + REDIS_ASSERT(len2 == len1 + val_size, "failed to append to non-empty string"); + + auto len3 = _redis.append(key, {}); + REDIS_ASSERT(len3 == len2, "failed to append empty string"); + + auto len4 = _redis.strlen(key); + REDIS_ASSERT(len4 == len3, "failed to test strlen"); + + REDIS_ASSERT(_redis.del(key) == 1, "failed to remove key"); + + auto len5 = _redis.append(key, {}); + REDIS_ASSERT(len5 == 0, "failed to append empty string to non-existent key"); + + _redis.del(key); + + REDIS_ASSERT(_redis.getrange(key, 0, 2) == "", "failed to test getrange on non-existent key"); + + _redis.set(key, val); + + REDIS_ASSERT(_redis.getrange(key, 1, 2) == val.substr(1, 2), "failed to test getrange"); + + long long new_size = val.size() * 2; + REDIS_ASSERT(_redis.setrange(key, val.size(), val) == new_size, "failed to test setrange"); + REDIS_ASSERT(_redis.getrange(key, 0, -1) == val + val, "failed to test setrange"); +} + +template +void StringCmdTest::_test_bit() { + auto key = test_key("bit"); + + KeyDeleter deleter(_redis, key); + + REDIS_ASSERT(_redis.bitcount(key) == 0, "failed to test bitcount on non-existent key"); + + REDIS_ASSERT(_redis.getbit(key, 5) == 0, "failed to test getbit"); + + REDIS_ASSERT(_redis.template command("SETBIT", key, 1, 1) == 0, + "failed to test setbit"); + REDIS_ASSERT(_redis.template command("SETBIT", key, 3, 1) == 0, + "failed to test setbit"); + REDIS_ASSERT(_redis.template command("SETBIT", key, 7, 1) == 0, + "failed to test setbit"); + REDIS_ASSERT(_redis.template command("SETBIT", key, 10, 1) == 0, + "failed to test setbit"); + REDIS_ASSERT(_redis.template command("SETBIT", key, 10, 0) == 1, + "failed to test setbit"); + REDIS_ASSERT(_redis.template command("SETBIT", key, 11, 1) == 0, + "failed to test setbit"); + REDIS_ASSERT(_redis.template command("SETBIT", key, 21, 1) == 0, + "failed to test setbit"); + + // key -> 01010001, 00010000, 00000100 + + REDIS_ASSERT(_redis.getbit(key, 1) == 1, "failed to test getbit"); + REDIS_ASSERT(_redis.getbit(key, 2) == 0, "failed to test getbit"); + REDIS_ASSERT(_redis.getbit(key, 7) == 1, "failed to test getbit"); + REDIS_ASSERT(_redis.getbit(key, 10) == 0, "failed to test getbit"); + REDIS_ASSERT(_redis.getbit(key, 100) == 0, "failed to test getbit"); + + REDIS_ASSERT(_redis.bitcount(key) == 5, "failed to test bitcount"); + REDIS_ASSERT(_redis.bitcount(key, 0, 0) == 3, "failed to test bitcount"); + REDIS_ASSERT(_redis.bitcount(key, 0, 1) == 4, "failed to test bitcount"); + REDIS_ASSERT(_redis.bitcount(key, -2, -1) == 2, "failed to test bitcount"); + + REDIS_ASSERT(_redis.bitpos(key, 1) == 1, "failed to test bitpos"); + REDIS_ASSERT(_redis.bitpos(key, 0) == 0, "failed to test bitpos"); + REDIS_ASSERT(_redis.bitpos(key, 1, 1, 1) == 11, "failed to test bitpos"); + REDIS_ASSERT(_redis.bitpos(key, 0, 1, 1) == 8, "failed to test bitpos"); + REDIS_ASSERT(_redis.bitpos(key, 1, -1, -1) == 21, "failed to test bitpos"); + REDIS_ASSERT(_redis.bitpos(key, 0, -1, -1) == 16, "failed to test bitpos"); + + auto dest_key = test_key("bitop_dest"); + auto src_key1 = test_key("bitop_src1"); + auto src_key2 = test_key("bitop_src2"); + + KeyDeleter deleters(_redis, {dest_key, src_key1, src_key2}); + + // src_key1 -> 00010000 + _redis.template command("SETBIT", src_key1, 3, 1); + + // src_key2 -> 00000000, 00001000 + _redis.template command("SETBIT", src_key2, 12, 1); + + REDIS_ASSERT(_redis.bitop(BitOp::AND, dest_key, {src_key1, src_key2}) == 2, + "failed to test bitop"); + + // dest_key -> 00000000, 00000000 + auto v = _redis.get(dest_key); + REDIS_ASSERT(v && *v == std::string(2, 0), "failed to test bitop"); + + REDIS_ASSERT(_redis.bitop(BitOp::NOT, dest_key, src_key1) == 1, + "failed to test bitop"); + + // dest_key -> 11101111 + v = _redis.get(dest_key); + REDIS_ASSERT(v && *v == std::string(1, 0xEF), "failed to test bitop"); +} + +template +void StringCmdTest::_test_numeric() { + auto key = test_key("numeric"); + + KeyDeleter deleter(_redis, key); + + REDIS_ASSERT(_redis.incr(key) == 1, "failed to test incr"); + REDIS_ASSERT(_redis.decr(key) == 0, "failed to test decr"); + REDIS_ASSERT(_redis.incrby(key, 3) == 3, "failed to test incrby"); + REDIS_ASSERT(_redis.decrby(key, 3) == 0, "failed to test decrby"); + REDIS_ASSERT(_redis.incrby(key, -3) == -3, "failed to test incrby"); + REDIS_ASSERT(_redis.decrby(key, -3) == 0, "failed to test incrby"); + REDIS_ASSERT(_redis.incrbyfloat(key, 1.5) == 1.5, "failed to test incrbyfloat"); +} + +template +void StringCmdTest::_test_getset() { + auto key = test_key("getset"); + auto non_exist_key = test_key("non-existent"); + + KeyDeleter deleter(_redis, {key, non_exist_key}); + + std::string val("value"); + REDIS_ASSERT(_redis.set(key, val), "failed to test set"); + + auto v = _redis.get(key); + REDIS_ASSERT(v && *v == val, "failed to test get"); + + v = _redis.getset(key, val + val); + REDIS_ASSERT(v && *v == val, "failed to test get"); + + REDIS_ASSERT(!_redis.set(key, val, std::chrono::milliseconds(0), UpdateType::NOT_EXIST), + "failed to test set with NOT_EXIST type"); + REDIS_ASSERT(!_redis.set(non_exist_key, val, std::chrono::milliseconds(0), UpdateType::EXIST), + "failed to test set with EXIST type"); + + REDIS_ASSERT(!_redis.setnx(key, val), "failed to test setnx"); + REDIS_ASSERT(_redis.setnx(non_exist_key, val), "failed to test setnx"); + + auto ttl = std::chrono::seconds(10); + + _redis.set(key, val, ttl); + REDIS_ASSERT(_redis.ttl(key) <= ttl.count(), "failed to test set key with ttl"); + + _redis.setex(key, ttl, val); + REDIS_ASSERT(_redis.ttl(key) <= ttl.count(), "failed to test setex"); + + auto pttl = std::chrono::milliseconds(10000); + + _redis.psetex(key, ttl, val); + REDIS_ASSERT(_redis.pttl(key) <= pttl.count(), "failed to test psetex"); +} + +template +void StringCmdTest::_test_mgetset() { + auto kvs = {std::make_pair(test_key("k1"), "v1"), + std::make_pair(test_key("k2"), "v2"), + std::make_pair(test_key("k3"), "v3")}; + + std::vector keys; + std::vector vals; + for (const auto &kv : kvs) { + keys.push_back(kv.first); + vals.push_back(kv.second); + } + + KeyDeleter deleter(_redis, keys.begin(), keys.end()); + + _redis.mset(kvs); + + std::vector res; + _redis.mget(keys.begin(), keys.end(), std::back_inserter(res)); + + REDIS_ASSERT(res.size() == kvs.size(), "failed to test mget"); + + std::vector res_vals; + for (const auto &ele : res) { + REDIS_ASSERT(bool(ele), "failed to test mget"); + + res_vals.push_back(*ele); + } + + REDIS_ASSERT(vals == res_vals, "failed to test mget"); + + REDIS_ASSERT(!_redis.msetnx(kvs), "failed to test msetnx"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_STRING_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/test_main.cpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/test_main.cpp new file mode 100644 index 000000000..dd2880d1a --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/test_main.cpp @@ -0,0 +1,303 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include +#include +#include +#include +#include +#include "sanity_test.h" +#include "connection_cmds_test.h" +#include "keys_cmds_test.h" +#include "string_cmds_test.h" +#include "list_cmds_test.h" +#include "hash_cmds_test.h" +#include "set_cmds_test.h" +#include "zset_cmds_test.h" +#include "hyperloglog_cmds_test.h" +#include "geo_cmds_test.h" +#include "script_cmds_test.h" +#include "pubsub_test.h" +#include "pipeline_transaction_test.h" +#include "threads_test.h" +#include "stream_cmds_test.h" +#include "benchmark_test.h" + +namespace { + +void print_help(); + +auto parse_options(int argc, char **argv) + -> std::tuple, + sw::redis::Optional, + sw::redis::Optional>; + +template +void run_test(const sw::redis::ConnectionOptions &opts); + +template +void run_benchmark(const sw::redis::ConnectionOptions &opts, + const sw::redis::test::BenchmarkOptions &benchmark_opts); + +} + +int main(int argc, char **argv) { + try { + sw::redis::Optional opts; + sw::redis::Optional cluster_node_opts; + sw::redis::Optional benchmark_opts; + std::tie(opts, cluster_node_opts, benchmark_opts) = parse_options(argc, argv); + + if (opts) { + std::cout << "Testing Redis..." << std::endl; + + if (benchmark_opts) { + run_benchmark(*opts, *benchmark_opts); + } else { + run_test(*opts); + } + } + + if (cluster_node_opts) { + std::cout << "Testing RedisCluster..." << std::endl; + + if (benchmark_opts) { + run_benchmark(*cluster_node_opts, *benchmark_opts); + } else { + run_test(*cluster_node_opts); + } + } + + std::cout << "Pass all tests" << std::endl; + } catch (const sw::redis::Error &e) { + std::cerr << "Test failed: " << e.what() << std::endl; + return -1; + } + + return 0; +} + +namespace { + +void print_help() { + std::cerr << "Usage: test_redis++ -h host -p port" + << " -n cluster_node -c cluster_port [-a auth] [-b]\n\n"; + std::cerr << "See https://github.com/sewenew/redis-plus-plus#run-tests-optional" + << " for details on how to run test" << std::endl; +} + +auto parse_options(int argc, char **argv) + -> std::tuple, + sw::redis::Optional, + sw::redis::Optional> { + std::string host; + int port = 0; + std::string auth; + std::string cluster_node; + int cluster_port = 0; + bool benchmark = false; + sw::redis::test::BenchmarkOptions tmp_benchmark_opts; + + int opt = 0; + while ((opt = getopt(argc, argv, "h:p:a:n:c:k:v:r:t:bs:")) != -1) { + try { + switch (opt) { + case 'h': + host = optarg; + break; + + case 'p': + port = std::stoi(optarg); + break; + + case 'a': + auth = optarg; + break; + + case 'n': + cluster_node = optarg; + break; + + case 'c': + cluster_port = std::stoi(optarg); + break; + + case 'b': + benchmark = true; + break; + + case 'k': + tmp_benchmark_opts.key_len = std::stoi(optarg); + break; + + case 'v': + tmp_benchmark_opts.val_len = std::stoi(optarg); + break; + + case 'r': + tmp_benchmark_opts.total_request_num = std::stoi(optarg); + break; + + case 't': + tmp_benchmark_opts.thread_num = std::stoi(optarg); + break; + + case 's': + tmp_benchmark_opts.pool_size = std::stoi(optarg); + break; + + default: + throw sw::redis::Error("Unknow command line option"); + break; + } + } catch (const sw::redis::Error &e) { + print_help(); + throw; + } catch (const std::exception &e) { + print_help(); + throw sw::redis::Error("Invalid command line option"); + } + } + + sw::redis::Optional opts; + if (!host.empty() && port > 0) { + sw::redis::ConnectionOptions tmp; + tmp.host = host; + tmp.port = port; + tmp.password = auth; + + opts = sw::redis::Optional(tmp); + } + + sw::redis::Optional cluster_opts; + if (!cluster_node.empty() && cluster_port > 0) { + sw::redis::ConnectionOptions tmp; + tmp.host = cluster_node; + tmp.port = cluster_port; + tmp.password = auth; + + cluster_opts = sw::redis::Optional(tmp); + } + + if (!opts && !cluster_opts) { + print_help(); + throw sw::redis::Error("Invalid connection options"); + } + + sw::redis::Optional benchmark_opts; + if (benchmark) { + benchmark_opts = sw::redis::Optional(tmp_benchmark_opts); + } + + return std::make_tuple(std::move(opts), std::move(cluster_opts), std::move(benchmark_opts)); +} + +template +void run_test(const sw::redis::ConnectionOptions &opts) { + auto instance = RedisInstance(opts); + + sw::redis::test::SanityTest sanity_test(opts, instance); + sanity_test.run(); + + std::cout << "Pass sanity tests" << std::endl; + + sw::redis::test::ConnectionCmdTest connection_test(instance); + connection_test.run(); + + std::cout << "Pass connection commands tests" << std::endl; + + sw::redis::test::KeysCmdTest keys_test(instance); + keys_test.run(); + + std::cout << "Pass keys commands tests" << std::endl; + + sw::redis::test::StringCmdTest string_test(instance); + string_test.run(); + + std::cout << "Pass string commands tests" << std::endl; + + sw::redis::test::ListCmdTest list_test(instance); + list_test.run(); + + std::cout << "Pass list commands tests" << std::endl; + + sw::redis::test::HashCmdTest hash_test(instance); + hash_test.run(); + + std::cout << "Pass hash commands tests" << std::endl; + + sw::redis::test::SetCmdTest set_test(instance); + set_test.run(); + + std::cout << "Pass set commands tests" << std::endl; + + sw::redis::test::ZSetCmdTest zset_test(instance); + zset_test.run(); + + std::cout << "Pass zset commands tests" << std::endl; + + sw::redis::test::HyperloglogCmdTest hll_test(instance); + hll_test.run(); + + std::cout << "Pass hyperloglog commands tests" << std::endl; + + sw::redis::test::GeoCmdTest geo_test(instance); + geo_test.run(); + + std::cout << "Pass geo commands tests" << std::endl; + + sw::redis::test::ScriptCmdTest script_test(instance); + script_test.run(); + + std::cout << "Pass script commands tests" << std::endl; + + sw::redis::test::PubSubTest pubsub_test(instance); + pubsub_test.run(); + + std::cout << "Pass pubsub tests" << std::endl; + + sw::redis::test::PipelineTransactionTest pipe_tx_test(instance); + pipe_tx_test.run(); + + std::cout << "Pass pipeline and transaction tests" << std::endl; + + sw::redis::test::ThreadsTest threads_test(opts); + threads_test.run(); + + std::cout << "Pass threads tests" << std::endl; + + sw::redis::test::StreamCmdsTest stream_test(instance); + stream_test.run(); + + std::cout << "Pass stream commands tests" << std::endl; +} + +template +void run_benchmark(const sw::redis::ConnectionOptions &opts, + const sw::redis::test::BenchmarkOptions &benchmark_opts) { + std::cout << "Benchmark test options:" << std::endl; + std::cout << " Thread number: " << benchmark_opts.thread_num << std::endl; + std::cout << " Connection pool size: " << benchmark_opts.pool_size << std::endl; + std::cout << " Length of key: " << benchmark_opts.key_len << std::endl; + std::cout << " Length of value: " << benchmark_opts.val_len << std::endl; + + auto instance = RedisInstance(opts); + + sw::redis::test::BenchmarkTest benchmark_test(benchmark_opts, instance); + benchmark_test.run(); +} + +} diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.h new file mode 100644 index 000000000..aee307ec2 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.h @@ -0,0 +1,51 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_THREADS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_THREADS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class ThreadsTest { +public: + explicit ThreadsTest(const ConnectionOptions &opts) : _opts(opts) {} + + void run(); + +private: + void _test_multithreads(RedisInstance redis, int threads_num, int times); + + void _test_timeout(); + + ConnectionOptions _opts; +}; + +} + +} + +} + +#include "threads_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_THREADS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.hpp new file mode 100644 index 000000000..24bee9454 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.hpp @@ -0,0 +1,147 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_THREADS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_THREADS_TEST_HPP + +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void ThreadsTest::run() { + // 100 * 10000 = 1 million writes + auto thread_num = 100; + auto times = 10000; + + // Default pool options: single connection and wait forever. + _test_multithreads(RedisInstance(_opts), thread_num, times); + + // Pool with 10 connections. + ConnectionPoolOptions pool_opts; + pool_opts.size = 10; + _test_multithreads(RedisInstance(_opts, pool_opts), thread_num, times); + + _test_timeout(); +} + +template +void ThreadsTest::_test_multithreads(RedisInstance redis, + int thread_num, + int times) { + std::vector keys; + keys.reserve(thread_num); + for (auto idx = 0; idx != thread_num; ++idx) { + auto key = test_key("multi-threads::" + std::to_string(idx)); + keys.push_back(key); + } + + using DeleterUPtr = std::unique_ptr>; + std::vector deleters; + for (const auto &key : keys) { + deleters.emplace_back(new KeyDeleter(redis, key)); + } + + std::vector workers; + workers.reserve(thread_num); + for (const auto &key : keys) { + workers.emplace_back([&redis, key, times]() { + try { + for (auto i = 0; i != times; ++i) { + redis.incr(key); + } + } catch (...) { + // Something bad happens. + return; + } + }); + } + + for (auto &worker : workers) { + worker.join(); + } + + for (const auto &key : keys) { + auto val = redis.get(key); + REDIS_ASSERT(bool(val), "failed to test multithreads, cannot get value of " + key); + + auto num = std::stoi(*val); + REDIS_ASSERT(num == times, "failed to test multithreads, num: " + + *val + ", times: " + std::to_string(times)); + } +} + +template +void ThreadsTest::_test_timeout() { + using namespace std::chrono; + + ConnectionPoolOptions pool_opts; + pool_opts.size = 1; + pool_opts.wait_timeout = milliseconds(100); + + auto redis = RedisInstance(_opts, pool_opts); + + auto key = test_key("key"); + + std::atomic slow_get_is_running{false}; + auto slow_get = [&slow_get_is_running](Connection &connection, const StringView &key) { + slow_get_is_running = true; + + // Sleep a while to simulate a slow get. + std::this_thread::sleep_for(seconds(5)); + + connection.send("GET %b", key.data(), key.size()); + }; + auto slow_get_thread = std::thread([&redis, slow_get, &key]() { + redis.command(slow_get, key); + }); + + auto get_thread = std::thread([&redis, &slow_get_is_running, &key]() { + try { + while (!slow_get_is_running) { + std::this_thread::sleep_for(milliseconds(10)); + } + + redis.get(key); + + // Slow get is running, this thread should + // timeout before obtaining the connection. + // So it never reaches here. + REDIS_ASSERT(false, "failed to test pool timeout"); + } catch (const Error &err) { + // This thread timeout. + } + }); + + slow_get_thread.join(); + get_thread.join(); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_THREADS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/utils.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/utils.h new file mode 100644 index 000000000..7c430e2d4 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/utils.h @@ -0,0 +1,96 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_UTILS_H +#define SEWENEW_REDISPLUSPLUS_TEST_UTILS_H + +#include +#include +#include + +#define REDIS_ASSERT(condition, msg) \ + sw::redis::test::redis_assert((condition), (msg), __FILE__, __LINE__) + +namespace sw { + +namespace redis { + +namespace test { + +inline void redis_assert(bool condition, + const std::string &msg, + const std::string &file, + int line) { + if (!condition) { + auto err_msg = "ASSERT: " + msg + ". " + file + ":" + std::to_string(line); + throw Error(err_msg); + } +} + +inline std::string test_key(const std::string &k) { + // Key prefix with hash tag, + // so that we can call multiple-key commands on RedisCluster. + return "{sw::redis::test}::" + k; +} + +template +void cluster_specializing_test(Test &test, void (Test::*func)(Redis &instance), Redis &instance) { + (test.*func)(instance); +} + +template +void cluster_specializing_test(Test &test, + void (Test::*func)(Redis &instance), + RedisCluster &cluster) { + auto instance = cluster.redis("hash-tag"); + (test.*func)(instance); +} + +template +class KeyDeleter { +public: + template + KeyDeleter(RedisInstance &redis, Input first, Input last) : _redis(redis), _keys(first, last) { + _delete(); + } + + KeyDeleter(RedisInstance &redis, std::initializer_list il) : + KeyDeleter(redis, il.begin(), il.end()) {} + + KeyDeleter(RedisInstance &redis, const std::string &key) : KeyDeleter(redis, {key}) {} + + ~KeyDeleter() { + _delete(); + } + +private: + void _delete() { + if (!_keys.empty()) { + _redis.del(_keys.begin(), _keys.end()); + } + } + + RedisInstance &_redis; + std::vector _keys; +}; + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_UTILS_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.h new file mode 100644 index 000000000..56fd0b96f --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.h @@ -0,0 +1,61 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_ZSET_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_ZSET_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class ZSetCmdTest { +public: + explicit ZSetCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_zset(); + + void _test_zscan(); + + void _test_range(); + + void _test_lex(); + + void _test_multi_zset(); + + void _test_zpop(); + + void _test_bzpop(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "zset_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_ZSET_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.hpp new file mode 100644 index 000000000..fb331bb0a --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.hpp @@ -0,0 +1,350 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_ZSET_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_ZSET_CMDS_TEST_HPP + +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void ZSetCmdTest::run() { + _test_zset(); + + _test_zscan(); + + _test_range(); + + _test_lex(); + + _test_multi_zset(); + + _test_zpop(); + + _test_bzpop(); +} + +template +void ZSetCmdTest::_test_zset() { + auto key = test_key("zset"); + + KeyDeleter deleter(_redis, key); + + std::map s = { + std::make_pair("m1", 1.2), + std::make_pair("m2", 2), + std::make_pair("m3", 3), + }; + + const auto &ele = *(s.begin()); + REDIS_ASSERT(_redis.zadd(key, ele.first, ele.second, UpdateType::EXIST) == 0, + "failed to test zadd with noexistent member"); + + REDIS_ASSERT(_redis.zadd(key, s.begin(), s.end()) == 3, "failed to test zadd"); + + REDIS_ASSERT(_redis.zadd(key, ele.first, ele.second, UpdateType::NOT_EXIST) == 0, + "failed to test zadd with exist member"); + + REDIS_ASSERT(_redis.zadd(key, s.begin(), s.end(), UpdateType::ALWAYS, true) == 0, + "failed to test zadd"); + + REDIS_ASSERT(_redis.zcard(key) == 3, "failed to test zcard"); + + auto rank = _redis.zrank(key, "m2"); + REDIS_ASSERT(bool(rank) && *rank == 1, "failed to test zrank"); + rank = _redis.zrevrank(key, "m4"); + REDIS_ASSERT(!rank, "failed to test zrevrank with nonexistent member"); + + auto score = _redis.zscore(key, "m4"); + REDIS_ASSERT(!score, "failed to test zscore with nonexistent member"); + + REDIS_ASSERT(_redis.zincrby(key, 1, "m3") == 4, "failed to test zincrby"); + + score = _redis.zscore(key, "m3"); + REDIS_ASSERT(score && *score == 4, "failed to test zscore"); + + REDIS_ASSERT(_redis.zrem(key, "m1") == 1, "failed to test zrem"); + REDIS_ASSERT(_redis.zrem(key, {"m1", "m2", "m3", "m4"}) == 2, "failed to test zrem"); +} + +template +void ZSetCmdTest::_test_zscan() { + auto key = test_key("zscan"); + + KeyDeleter deleter(_redis, key); + + std::map s = { + std::make_pair("m1", 1.2), + std::make_pair("m2", 2), + std::make_pair("m3", 3), + }; + _redis.zadd(key, s.begin(), s.end()); + + std::map res; + auto cursor = 0; + while (true) { + cursor = _redis.zscan(key, cursor, "m*", 2, std::inserter(res, res.end())); + if (cursor == 0) { + break; + } + } + REDIS_ASSERT(res == s, "failed to test zscan"); +} + +template +void ZSetCmdTest::_test_range() { + auto key = test_key("range"); + + KeyDeleter deleter(_redis, key); + + std::map s = { + std::make_pair("m1", 1), + std::make_pair("m2", 2), + std::make_pair("m3", 3), + }; + _redis.zadd(key, s.begin(), s.end()); + + REDIS_ASSERT(_redis.zcount(key, UnboundedInterval{}) == 3, "failed to test zcount"); + + std::vector members; + _redis.zrange(key, 0, -1, std::back_inserter(members)); + REDIS_ASSERT(members.size() == s.size(), "failed to test zrange"); + for (const auto &mem : {"m1", "m2", "m3"}) { + REDIS_ASSERT(std::find(members.begin(), members.end(), mem) != members.end(), + "failed to test zrange"); + } + + std::map res; + _redis.zrange(key, 0, -1, std::inserter(res, res.end())); + REDIS_ASSERT(s == res, "failed to test zrange with score"); + + members.clear(); + _redis.zrevrange(key, 0, 0, std::back_inserter(members)); + REDIS_ASSERT(members.size() == 1 && members[0] == "m3", "failed to test zrevrange"); + + res.clear(); + _redis.zrevrange(key, 0, 0, std::inserter(res, res.end())); + REDIS_ASSERT(res.size() == 1 && res.find("m3") != res.end() && res["m3"] == 3, + "failed to test zrevrange with score"); + + members.clear(); + _redis.zrangebyscore(key, UnboundedInterval{}, std::back_inserter(members)); + REDIS_ASSERT(members.size() == s.size(), "failed to test zrangebyscore"); + for (const auto &mem : {"m1", "m2", "m3"}) { + REDIS_ASSERT(std::find(members.begin(), members.end(), mem) != members.end(), + "failed to test zrangebyscore"); + } + + members.clear(); + _redis.zrangebyscore(key, + BoundedInterval(1, 2, BoundType::RIGHT_OPEN), + std::back_inserter(members)); + REDIS_ASSERT(members.size() == 1 && members[0] == "m1", "failed to test zrangebyscore"); + + res.clear(); + _redis.zrangebyscore(key, + LeftBoundedInterval(2, BoundType::OPEN), + std::inserter(res, res.end())); + REDIS_ASSERT(res.size() == 1 && res.find("m3") != res.end() && res["m3"] == 3, + "failed to test zrangebyscore"); + + members.clear(); + _redis.zrevrangebyscore(key, + BoundedInterval(1, 3, BoundType::CLOSED), + std::back_inserter(members)); + REDIS_ASSERT(members == std::vector({"m3", "m2", "m1"}), + "failed to test zrevrangebyscore"); + + res.clear(); + _redis.zrevrangebyscore(key, + RightBoundedInterval(1, BoundType::LEFT_OPEN), + std::inserter(res, res.end())); + REDIS_ASSERT(res.size() == 1 && res.find("m1") != res.end() && res["m1"] == 1, + "failed to test zrevrangebyscore"); + + REDIS_ASSERT(_redis.zremrangebyrank(key, 0, 0) == 1, "failed to test zremrangebyrank"); + + REDIS_ASSERT(_redis.zremrangebyscore(key, + BoundedInterval(2, 3, BoundType::LEFT_OPEN)) == 1, + "failed to test zremrangebyscore"); +} + +template +void ZSetCmdTest::_test_lex() { + auto key = test_key("lex"); + + KeyDeleter deleter(_redis, key); + + auto s = { + std::make_pair("m1", 0), + std::make_pair("m2", 0), + std::make_pair("m3", 0), + }; + _redis.zadd(key, s.begin(), s.end()); + + REDIS_ASSERT(_redis.zlexcount(key, UnboundedInterval{}) == 3, + "failed to test zlexcount"); + + std::vector members; + _redis.zrangebylex(key, + LeftBoundedInterval("m2", BoundType::OPEN), + std::back_inserter(members)); + REDIS_ASSERT(members.size() == 1 && members[0] == "m3", + "failed to test zrangebylex"); + + members.clear(); + _redis.zrevrangebylex(key, + RightBoundedInterval("m1", BoundType::LEFT_OPEN), + std::back_inserter(members)); + REDIS_ASSERT(members.size() == 1 && members[0] == "m1", + "failed to test zrevrangebylex"); + + REDIS_ASSERT(_redis.zremrangebylex(key, + BoundedInterval("m1", "m3", BoundType::OPEN)) == 1, + "failed to test zremrangebylex"); +} + +template +void ZSetCmdTest::_test_multi_zset() { + auto k1 = test_key("k1"); + auto k2 = test_key("k2"); + auto k3 = test_key("k3"); + + KeyDeleter deleter(_redis, {k1, k2, k3}); + + _redis.zadd(k1, {std::make_pair("a", 1), std::make_pair("b", 2)}); + _redis.zadd(k2, {std::make_pair("a", 2), std::make_pair("c", 3)}); + + REDIS_ASSERT(_redis.zinterstore(k3, {k1, k2}) == 1, "failed to test zinterstore"); + auto score = _redis.zscore(k3, "a"); + REDIS_ASSERT(bool(score) && *score == 3, "failed to test zinterstore"); + + REDIS_ASSERT(_redis.zinterstore(k3, k1, 2) == 2, "failed to test zinterstore"); + + _redis.del(k3); + + REDIS_ASSERT(_redis.zinterstore(k3, {k1, k2}, Aggregation::MAX) == 1, + "failed to test zinterstore"); + score = _redis.zscore(k3, "a"); + REDIS_ASSERT(bool(score) && *score == 2, "failed to test zinterstore"); + + _redis.del(k3); + + REDIS_ASSERT(_redis.zunionstore(k3, + {std::make_pair(k1, 1), std::make_pair(k2, 2)}, + Aggregation::MIN) == 3, + "failed to test zunionstore"); + std::vector> res; + _redis.zrange(k3, 0, -1, std::back_inserter(res)); + for (const auto &ele : res) { + if (ele.first == "a") { + REDIS_ASSERT(ele.second == 1, "failed to test zunionstore"); + } else if (ele.first == "b") { + REDIS_ASSERT(ele.second == 2, "failed to test zunionstore"); + } else if (ele.first == "c") { + REDIS_ASSERT(ele.second == 6, "failed to test zunionstore"); + } else { + REDIS_ASSERT(false, "failed to test zuionstore"); + } + } + + REDIS_ASSERT(_redis.zunionstore(k3, k1, 2) == 2, "failed to test zunionstore"); +} + +template +void ZSetCmdTest::_test_zpop() { + auto key = test_key("zpop"); + + KeyDeleter deleter(_redis, key); + + _redis.zadd(key, {std::make_pair("m1", 1.1), + std::make_pair("m2", 2.2), + std::make_pair("m3", 3.3), + std::make_pair("m4", 4.4), + std::make_pair("m5", 5.5), + std::make_pair("m6", 6.6)}); + + auto item = _redis.zpopmax(key); + REDIS_ASSERT(item && item->first == "m6", "failed to test zpopmax"); + + item = _redis.zpopmin(key); + REDIS_ASSERT(item && item->first == "m1", "failed to test zpopmin"); + + std::vector> vec; + _redis.zpopmax(key, 2, std::back_inserter(vec)); + REDIS_ASSERT(vec.size() == 2 && vec[0].first == "m5" && vec[1].first == "m4", + "failed to test zpopmax"); + + std::unordered_map m; + _redis.zpopmin(key, 2, std::inserter(m, m.end())); + REDIS_ASSERT(m.size() == 2 && m.find("m3") != m.end() && m.find("m2") != m.end(), + "failed to test zpopmin"); +} + +template +void ZSetCmdTest::_test_bzpop() { + auto key1 = test_key("bzpop1"); + auto key2 = test_key("bzpop2"); + + KeyDeleter deleter(_redis, {key1, key2}); + + _redis.zadd(key1, {std::make_pair("m1", 1.1), + std::make_pair("m2", 2.2), + std::make_pair("m3", 3.3), + std::make_pair("m4", 4.4), + std::make_pair("m5", 5.5), + std::make_pair("m6", 6.6)}); + + _redis.zadd(key2, {std::make_pair("m1", 1.1), + std::make_pair("m2", 2.2), + std::make_pair("m3", 3.3), + std::make_pair("m4", 4.4), + std::make_pair("m5", 5.5), + std::make_pair("m6", 6.6)}); + + auto item = _redis.bzpopmax(key1); + REDIS_ASSERT(item && std::get<0>(*item) == key1 && std::get<1>(*item) == "m6", + "failed to test bzpopmax"); + + item = _redis.bzpopmin(key1, std::chrono::seconds(1)); + REDIS_ASSERT(item && std::get<0>(*item) == key1 && std::get<1>(*item) == "m1", + "failed to test zpopmin"); + + item = _redis.bzpopmax({key1, key2}, std::chrono::seconds(1)); + REDIS_ASSERT(item && std::get<0>(*item) == key1 && std::get<1>(*item) == "m5", + "failed to test zpopmax"); + + item = _redis.bzpopmin({key2, key1}); + REDIS_ASSERT(item && std::get<0>(*item) == key2 && std::get<1>(*item) == "m1", + "failed to test zpopmin"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_ZSET_CMDS_TEST_HPP From d699116795b5e1db30798b8abb59b4fa3e70ea13 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 11 May 2020 16:02:49 -0700 Subject: [PATCH 051/362] mac deps --- controller/PostgreSQL.cpp | 22 +- controller/PostgreSQL.hpp | 4 + controller/Redis.hpp | 15 + ext/hiredis-0.14.1/.gitignore | 1 - ext/hiredis-0.14.1/include/hiredis/alloc.h | 53 + .../include/hiredis}/async.h | 2 +- .../include/hiredis}/dict.h | 0 ext/hiredis-0.14.1/include/hiredis/fmacros.h | 12 + .../include/hiredis}/hiredis.h | 35 +- .../include/hiredis}/net.h | 4 - .../include/hiredis}/read.h | 26 +- ext/hiredis-0.14.1/include/hiredis/sds.h | 273 + ext/hiredis-0.14.1/include/hiredis/sdsalloc.h | 42 + .../include/hiredis}/win32.h | 0 ext/hiredis-vip-0.3.0/.gitignore | 7 - ext/hiredis-vip-0.3.0/.travis.yml | 16 - ext/hiredis-vip-0.3.0/CHANGELOG.md | 16 - ext/hiredis-vip-0.3.0/COPYING | 29 - ext/hiredis-vip-0.3.0/Makefile | 205 - ext/hiredis-vip-0.3.0/README.md | 255 - ext/hiredis-vip-0.3.0/adapters/ae.h | 154 - ext/hiredis-vip-0.3.0/adapters/glib.h | 153 - ext/hiredis-vip-0.3.0/adapters/libev.h | 147 - ext/hiredis-vip-0.3.0/adapters/libevent.h | 135 - ext/hiredis-vip-0.3.0/adapters/libuv.h | 122 - ext/hiredis-vip-0.3.0/adlist.c | 341 -- ext/hiredis-vip-0.3.0/adlist.h | 93 - ext/hiredis-vip-0.3.0/async.c | 691 --- ext/hiredis-vip-0.3.0/command.c | 1700 ------ ext/hiredis-vip-0.3.0/command.h | 179 - ext/hiredis-vip-0.3.0/crc16.c | 87 - ext/hiredis-vip-0.3.0/dict.c | 338 -- ext/hiredis-vip-0.3.0/examples/example-ae.c | 62 - ext/hiredis-vip-0.3.0/examples/example-glib.c | 73 - .../examples/example-libev.c | 52 - .../examples/example-libevent.c | 53 - .../examples/example-libuv.c | 53 - ext/hiredis-vip-0.3.0/examples/example.c | 78 - ext/hiredis-vip-0.3.0/fmacros.h | 23 - ext/hiredis-vip-0.3.0/hiarray.c | 188 - ext/hiredis-vip-0.3.0/hiarray.h | 56 - ext/hiredis-vip-0.3.0/hircluster.c | 4747 ----------------- ext/hiredis-vip-0.3.0/hircluster.h | 178 - ext/hiredis-vip-0.3.0/hiredis.c | 1021 ---- ext/hiredis-vip-0.3.0/hiutil.c | 554 -- ext/hiredis-vip-0.3.0/hiutil.h | 265 - ext/hiredis-vip-0.3.0/net.c | 458 -- ext/hiredis-vip-0.3.0/read.c | 525 -- ext/hiredis-vip-0.3.0/sds.c | 1095 ---- ext/hiredis-vip-0.3.0/sds.h | 105 - ext/hiredis-vip-0.3.0/test.c | 806 --- make-mac.mk | 5 +- objects.mk | 1 - 53 files changed, 434 insertions(+), 15121 deletions(-) create mode 100644 controller/Redis.hpp create mode 100644 ext/hiredis-0.14.1/include/hiredis/alloc.h rename ext/{hiredis-vip-0.3.0 => hiredis-0.14.1/include/hiredis}/async.h (98%) rename ext/{hiredis-vip-0.3.0 => hiredis-0.14.1/include/hiredis}/dict.h (100%) create mode 100644 ext/hiredis-0.14.1/include/hiredis/fmacros.h rename ext/{hiredis-vip-0.3.0 => hiredis-0.14.1/include/hiredis}/hiredis.h (83%) rename ext/{hiredis-vip-0.3.0 => hiredis-0.14.1/include/hiredis}/net.h (97%) rename ext/{hiredis-vip-0.3.0 => hiredis-0.14.1/include/hiredis}/read.h (80%) create mode 100644 ext/hiredis-0.14.1/include/hiredis/sds.h create mode 100644 ext/hiredis-0.14.1/include/hiredis/sdsalloc.h rename ext/{hiredis-vip-0.3.0 => hiredis-0.14.1/include/hiredis}/win32.h (100%) delete mode 100644 ext/hiredis-vip-0.3.0/.gitignore delete mode 100644 ext/hiredis-vip-0.3.0/.travis.yml delete mode 100644 ext/hiredis-vip-0.3.0/CHANGELOG.md delete mode 100644 ext/hiredis-vip-0.3.0/COPYING delete mode 100644 ext/hiredis-vip-0.3.0/Makefile delete mode 100644 ext/hiredis-vip-0.3.0/README.md delete mode 100644 ext/hiredis-vip-0.3.0/adapters/ae.h delete mode 100644 ext/hiredis-vip-0.3.0/adapters/glib.h delete mode 100644 ext/hiredis-vip-0.3.0/adapters/libev.h delete mode 100644 ext/hiredis-vip-0.3.0/adapters/libevent.h delete mode 100644 ext/hiredis-vip-0.3.0/adapters/libuv.h delete mode 100644 ext/hiredis-vip-0.3.0/adlist.c delete mode 100644 ext/hiredis-vip-0.3.0/adlist.h delete mode 100644 ext/hiredis-vip-0.3.0/async.c delete mode 100644 ext/hiredis-vip-0.3.0/command.c delete mode 100644 ext/hiredis-vip-0.3.0/command.h delete mode 100644 ext/hiredis-vip-0.3.0/crc16.c delete mode 100644 ext/hiredis-vip-0.3.0/dict.c delete mode 100644 ext/hiredis-vip-0.3.0/examples/example-ae.c delete mode 100644 ext/hiredis-vip-0.3.0/examples/example-glib.c delete mode 100644 ext/hiredis-vip-0.3.0/examples/example-libev.c delete mode 100644 ext/hiredis-vip-0.3.0/examples/example-libevent.c delete mode 100644 ext/hiredis-vip-0.3.0/examples/example-libuv.c delete mode 100644 ext/hiredis-vip-0.3.0/examples/example.c delete mode 100644 ext/hiredis-vip-0.3.0/fmacros.h delete mode 100644 ext/hiredis-vip-0.3.0/hiarray.c delete mode 100644 ext/hiredis-vip-0.3.0/hiarray.h delete mode 100644 ext/hiredis-vip-0.3.0/hircluster.c delete mode 100644 ext/hiredis-vip-0.3.0/hircluster.h delete mode 100644 ext/hiredis-vip-0.3.0/hiredis.c delete mode 100644 ext/hiredis-vip-0.3.0/hiutil.c delete mode 100644 ext/hiredis-vip-0.3.0/hiutil.h delete mode 100644 ext/hiredis-vip-0.3.0/net.c delete mode 100644 ext/hiredis-vip-0.3.0/read.c delete mode 100644 ext/hiredis-vip-0.3.0/sds.c delete mode 100644 ext/hiredis-vip-0.3.0/sds.h delete mode 100644 ext/hiredis-vip-0.3.0/test.c diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 91cbdb789..0640cb8ee 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -78,6 +78,8 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, R , _waitNoticePrinted(false) , _listenPort(listenPort) , _rc(rc) + , _redis(NULL) + , _cluster(NULL) { char myAddress[64]; _myAddressStr = myId.address().toString(myAddress); @@ -113,6 +115,21 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, R PQfinish(conn); conn = NULL; + if (_rc != NULL) { + sw::redis::ConnectionOptions opts; + sw::redis::ConnectionPoolOptions poolOpts; + opts.host = _rc->hostname; + opts.port = _rc->port; + opts.password = _rc->password; + opts.db = 0; + poolOpts.size = 10; + if (_rc->clusterMode) { + _cluster = new sw::redis::RedisCluster(opts, poolOpts); + } else { + _redis = new sw::redis::Redis(opts, poolOpts); + } + } + _readyLock.lock(); _heartbeatThread = std::thread(&PostgreSQL::heartbeat, this); _membersDbWatcher = std::thread(&PostgreSQL::membersDbWatcher, this); @@ -128,6 +145,8 @@ PostgreSQL::~PostgreSQL() _run = 0; std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + _heartbeatThread.join(); _membersDbWatcher.join(); _networksDbWatcher.join(); @@ -135,7 +154,8 @@ PostgreSQL::~PostgreSQL() _commitThread[i].join(); } _onlineNotificationThread.join(); - + delete _redis; + delete _cluster; } diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index 5d14e2ff6..986559acf 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -20,6 +20,8 @@ #define ZT_CENTRAL_CONTROLLER_COMMIT_THREADS 4 +#include + extern "C" { typedef struct pg_conn PGconn; } @@ -98,6 +100,8 @@ private: int _listenPort; RedisConfig *_rc; + sw::redis::Redis *_redis; + sw::redis::RedisCluster *_cluster; }; } // namespace ZeroTier diff --git a/controller/Redis.hpp b/controller/Redis.hpp new file mode 100644 index 000000000..095419b01 --- /dev/null +++ b/controller/Redis.hpp @@ -0,0 +1,15 @@ +#ifndef ZT_CONTROLLER_REDIS_HPP +#define ZT_CONTROLLER_REDIS_HPP + +#include + +namespace ZeroTier { +struct RedisConfig { + std::string hostname; + int port; + std::string password; + bool clusterMode; +}; +} + +#endif \ No newline at end of file diff --git a/ext/hiredis-0.14.1/.gitignore b/ext/hiredis-0.14.1/.gitignore index c44b5c537..db2ad032a 100644 --- a/ext/hiredis-0.14.1/.gitignore +++ b/ext/hiredis-0.14.1/.gitignore @@ -3,5 +3,4 @@ /*.o /*.so /*.dylib -/*.a /*.pc diff --git a/ext/hiredis-0.14.1/include/hiredis/alloc.h b/ext/hiredis-0.14.1/include/hiredis/alloc.h new file mode 100644 index 000000000..2c9b04e35 --- /dev/null +++ b/ext/hiredis-0.14.1/include/hiredis/alloc.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020, Michael Grunder + * + * 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. + */ + +#ifndef HIREDIS_ALLOC_H +#define HIREDIS_ALLOC_H + +#include /* for size_t */ + +#ifndef HIREDIS_OOM_HANDLER +#define HIREDIS_OOM_HANDLER abort() +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void *hi_malloc(size_t size); +void *hi_calloc(size_t nmemb, size_t size); +void *hi_realloc(void *ptr, size_t size); +char *hi_strdup(const char *str); + +#ifdef __cplusplus +} +#endif + +#endif /* HIREDIS_ALLOC_H */ diff --git a/ext/hiredis-vip-0.3.0/async.h b/ext/hiredis-0.14.1/include/hiredis/async.h similarity index 98% rename from ext/hiredis-vip-0.3.0/async.h rename to ext/hiredis-0.14.1/include/hiredis/async.h index 2ba7142b8..e69d84090 100644 --- a/ext/hiredis-vip-0.3.0/async.h +++ b/ext/hiredis-0.14.1/include/hiredis/async.h @@ -45,6 +45,7 @@ typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); typedef struct redisCallback { struct redisCallback *next; /* simple singly linked list */ redisCallbackFn *fn; + int pending_subs; void *privdata; } redisCallback; @@ -68,7 +69,6 @@ typedef struct redisAsyncContext { /* Not used by hiredis */ void *data; - void (*dataHandler)(struct redisAsyncContext* ac); /* Event library data and hooks */ struct { diff --git a/ext/hiredis-vip-0.3.0/dict.h b/ext/hiredis-0.14.1/include/hiredis/dict.h similarity index 100% rename from ext/hiredis-vip-0.3.0/dict.h rename to ext/hiredis-0.14.1/include/hiredis/dict.h diff --git a/ext/hiredis-0.14.1/include/hiredis/fmacros.h b/ext/hiredis-0.14.1/include/hiredis/fmacros.h new file mode 100644 index 000000000..3227faafd --- /dev/null +++ b/ext/hiredis-0.14.1/include/hiredis/fmacros.h @@ -0,0 +1,12 @@ +#ifndef __HIREDIS_FMACRO_H +#define __HIREDIS_FMACRO_H + +#define _XOPEN_SOURCE 600 +#define _POSIX_C_SOURCE 200112L + +#if defined(__APPLE__) && defined(__MACH__) +/* Enable TCP_KEEPALIVE */ +#define _DARWIN_C_SOURCE +#endif + +#endif diff --git a/ext/hiredis-vip-0.3.0/hiredis.h b/ext/hiredis-0.14.1/include/hiredis/hiredis.h similarity index 83% rename from ext/hiredis-vip-0.3.0/hiredis.h rename to ext/hiredis-0.14.1/include/hiredis/hiredis.h index 87f7366f8..d945bf204 100644 --- a/ext/hiredis-vip-0.3.0/hiredis.h +++ b/ext/hiredis-0.14.1/include/hiredis/hiredis.h @@ -38,10 +38,12 @@ #include /* for struct timeval */ #include /* uintXX_t, etc */ #include "sds.h" /* for sds */ +#include "alloc.h" /* for allocation wrappers */ #define HIREDIS_MAJOR 0 -#define HIREDIS_MINOR 13 +#define HIREDIS_MINOR 14 #define HIREDIS_PATCH 1 +#define HIREDIS_SONAME 0.14 /* Connection type can be blocking or non-blocking and is set in the * least significant bit of the flags field in redisContext. */ @@ -79,30 +81,6 @@ * SO_REUSEADDR is being used. */ #define REDIS_CONNECT_RETRIES 10 -/* strerror_r has two completely different prototypes and behaviors - * depending on system issues, so we need to operate on the error buffer - * differently depending on which strerror_r we're using. */ -#ifndef _GNU_SOURCE -/* "regular" POSIX strerror_r that does the right thing. */ -#define __redis_strerror_r(errno, buf, len) \ - do { \ - strerror_r((errno), (buf), (len)); \ - } while (0) -#else -/* "bad" GNU strerror_r we need to clean up after. */ -#define __redis_strerror_r(errno, buf, len) \ - do { \ - char *err_str = strerror_r((errno), (buf), (len)); \ - /* If return value _isn't_ the start of the buffer we passed in, \ - * then GNU strerror_r returned an internal static buffer and we \ - * need to copy the result into our private buffer. */ \ - if (err_str != (buf)) { \ - buf[(len)] = '\0'; \ - strncat((buf), err_str, ((len) - 1)); \ - } \ - } while (0) -#endif - #ifdef __cplusplus extern "C" { #endif @@ -111,7 +89,7 @@ extern "C" { typedef struct redisReply { int type; /* REDIS_REPLY_* */ long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ - int len; /* Length of string */ + size_t len; /* Length of string */ char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ @@ -132,7 +110,7 @@ void redisFreeSdsCommand(sds cmd); enum redisConnectionType { REDIS_CONN_TCP, - REDIS_CONN_UNIX, + REDIS_CONN_UNIX }; /* Context for a connection to Redis */ @@ -156,6 +134,7 @@ typedef struct redisContext { struct { char *path; } unix_sock; + } redisContext; redisContext *redisConnect(const char *ip, int port); @@ -177,7 +156,7 @@ redisContext *redisConnectFd(int fd); * host, ip (or path), timeout and bind address are reused, * flags are used unmodified from the existing context. * - * Returns REDIS_OK on successfull connect or REDIS_ERR otherwise. + * Returns REDIS_OK on successful connect or REDIS_ERR otherwise. */ int redisReconnect(redisContext *c); diff --git a/ext/hiredis-vip-0.3.0/net.h b/ext/hiredis-0.14.1/include/hiredis/net.h similarity index 97% rename from ext/hiredis-vip-0.3.0/net.h rename to ext/hiredis-0.14.1/include/hiredis/net.h index 2f1a0bf85..d9dc36257 100644 --- a/ext/hiredis-vip-0.3.0/net.h +++ b/ext/hiredis-0.14.1/include/hiredis/net.h @@ -37,10 +37,6 @@ #include "hiredis.h" -#if defined(__sun) -#define AF_LOCAL AF_UNIX -#endif - int redisCheckSocketError(redisContext *c); int redisContextSetTimeout(redisContext *c, const struct timeval tv); int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); diff --git a/ext/hiredis-vip-0.3.0/read.h b/ext/hiredis-0.14.1/include/hiredis/read.h similarity index 80% rename from ext/hiredis-vip-0.3.0/read.h rename to ext/hiredis-0.14.1/include/hiredis/read.h index 088c97903..2988aa453 100644 --- a/ext/hiredis-vip-0.3.0/read.h +++ b/ext/hiredis-0.14.1/include/hiredis/read.h @@ -38,7 +38,7 @@ #define REDIS_OK 0 /* When an error occurs, the err flag in a context is set to hold the type of - * error that occured. REDIS_ERR_IO means there was an I/O error and you + * error that occurred. REDIS_ERR_IO means there was an I/O error and you * should use the "errno" variable to find out what is wrong. * For other values, the "errstr" field will hold a description. */ #define REDIS_ERR_IO 1 /* Error in read or write */ @@ -46,9 +46,6 @@ #define REDIS_ERR_PROTOCOL 4 /* Protocol error */ #define REDIS_ERR_OOM 5 /* Out of memory */ #define REDIS_ERR_OTHER 2 /* Everything else... */ -#if 1 //shenzheng 2015-8-10 redis cluster -#define REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT 6 -#endif //shenzheng 2015-8-10 redis cluster #define REDIS_REPLY_STRING 1 #define REDIS_REPLY_ARRAY 2 @@ -59,16 +56,6 @@ #define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ -#if 1 //shenzheng 2015-8-22 redis cluster -#define REDIS_ERROR_MOVED "MOVED" -#define REDIS_ERROR_ASK "ASK" -#define REDIS_ERROR_TRYAGAIN "TRYAGAIN" -#define REDIS_ERROR_CROSSSLOT "CROSSSLOT" -#define REDIS_ERROR_CLUSTERDOWN "CLUSTERDOWN" - -#define REDIS_STATUS_OK "OK" -#endif //shenzheng 2015-9-24 redis cluster - #ifdef __cplusplus extern "C" { #endif @@ -113,14 +100,9 @@ void redisReaderFree(redisReader *r); int redisReaderFeed(redisReader *r, const char *buf, size_t len); int redisReaderGetReply(redisReader *r, void **reply); -/* Backwards compatibility, can be removed on big version bump. */ -#define redisReplyReaderCreate redisReaderCreate -#define redisReplyReaderFree redisReaderFree -#define redisReplyReaderFeed redisReaderFeed -#define redisReplyReaderGetReply redisReaderGetReply -#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) -#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply) -#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr) +#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) +#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply) +#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr) #ifdef __cplusplus } diff --git a/ext/hiredis-0.14.1/include/hiredis/sds.h b/ext/hiredis-0.14.1/include/hiredis/sds.h new file mode 100644 index 000000000..13be75a9f --- /dev/null +++ b/ext/hiredis-0.14.1/include/hiredis/sds.h @@ -0,0 +1,273 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, Redis Labs, Inc + * 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. + */ + +#ifndef __SDS_H +#define __SDS_H + +#define SDS_MAX_PREALLOC (1024*1024) + +#include +#include +#include + +typedef char *sds; + +/* Note: sdshdr5 is never used, we just access the flags byte directly. + * However is here to document the layout of type 5 SDS strings. */ +struct __attribute__ ((__packed__)) sdshdr5 { + unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr8 { + uint8_t len; /* used */ + uint8_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr16 { + uint16_t len; /* used */ + uint16_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr32 { + uint32_t len; /* used */ + uint32_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr64 { + uint64_t len; /* used */ + uint64_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; + +#define SDS_TYPE_5 0 +#define SDS_TYPE_8 1 +#define SDS_TYPE_16 2 +#define SDS_TYPE_32 3 +#define SDS_TYPE_64 4 +#define SDS_TYPE_MASK 7 +#define SDS_TYPE_BITS 3 +#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))); +#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) +#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) + +static inline size_t sdslen(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->len; + case SDS_TYPE_16: + return SDS_HDR(16,s)->len; + case SDS_TYPE_32: + return SDS_HDR(32,s)->len; + case SDS_TYPE_64: + return SDS_HDR(64,s)->len; + } + return 0; +} + +static inline size_t sdsavail(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { + return 0; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + return sh->alloc - sh->len; + } + } + return 0; +} + +static inline void sdssetlen(sds s, size_t newlen) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + { + unsigned char *fp = ((unsigned char*)s)-1; + *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); + } + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->len = newlen; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->len = newlen; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->len = newlen; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->len = newlen; + break; + } +} + +static inline void sdsinclen(sds s, size_t inc) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + { + unsigned char *fp = ((unsigned char*)s)-1; + unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc; + *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); + } + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->len += inc; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->len += inc; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->len += inc; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->len += inc; + break; + } +} + +/* sdsalloc() = sdsavail() + sdslen() */ +static inline size_t sdsalloc(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->alloc; + case SDS_TYPE_16: + return SDS_HDR(16,s)->alloc; + case SDS_TYPE_32: + return SDS_HDR(32,s)->alloc; + case SDS_TYPE_64: + return SDS_HDR(64,s)->alloc; + } + return 0; +} + +static inline void sdssetalloc(sds s, size_t newlen) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + /* Nothing to do, this type has no total allocation info. */ + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->alloc = newlen; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->alloc = newlen; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->alloc = newlen; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->alloc = newlen; + break; + } +} + +sds sdsnewlen(const void *init, size_t initlen); +sds sdsnew(const char *init); +sds sdsempty(void); +sds sdsdup(const sds s); +void sdsfree(sds s); +sds sdsgrowzero(sds s, size_t len); +sds sdscatlen(sds s, const void *t, size_t len); +sds sdscat(sds s, const char *t); +sds sdscatsds(sds s, const sds t); +sds sdscpylen(sds s, const char *t, size_t len); +sds sdscpy(sds s, const char *t); + +sds sdscatvprintf(sds s, const char *fmt, va_list ap); +#ifdef __GNUC__ +sds sdscatprintf(sds s, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +#else +sds sdscatprintf(sds s, const char *fmt, ...); +#endif + +sds sdscatfmt(sds s, char const *fmt, ...); +sds sdstrim(sds s, const char *cset); +void sdsrange(sds s, int start, int end); +void sdsupdatelen(sds s); +void sdsclear(sds s); +int sdscmp(const sds s1, const sds s2); +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); +void sdsfreesplitres(sds *tokens, int count); +void sdstolower(sds s); +void sdstoupper(sds s); +sds sdsfromlonglong(long long value); +sds sdscatrepr(sds s, const char *p, size_t len); +sds *sdssplitargs(const char *line, int *argc); +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); +sds sdsjoin(char **argv, int argc, char *sep); +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); + +/* Low level functions exposed to the user API */ +sds sdsMakeRoomFor(sds s, size_t addlen); +void sdsIncrLen(sds s, int incr); +sds sdsRemoveFreeSpace(sds s); +size_t sdsAllocSize(sds s); +void *sdsAllocPtr(sds s); + +/* Export the allocator used by SDS to the program using SDS. + * Sometimes the program SDS is linked to, may use a different set of + * allocators, but may want to allocate or free things that SDS will + * respectively free or allocate. */ +void *sds_malloc(size_t size); +void *sds_realloc(void *ptr, size_t size); +void sds_free(void *ptr); + +#ifdef REDIS_TEST +int sdsTest(int argc, char *argv[]); +#endif + +#endif diff --git a/ext/hiredis-0.14.1/include/hiredis/sdsalloc.h b/ext/hiredis-0.14.1/include/hiredis/sdsalloc.h new file mode 100644 index 000000000..f43023c48 --- /dev/null +++ b/ext/hiredis-0.14.1/include/hiredis/sdsalloc.h @@ -0,0 +1,42 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, Redis Labs, Inc + * 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. + */ + +/* SDS allocator selection. + * + * This file is used in order to change the SDS allocator at compile time. + * Just define the following defines to what you want to use. Also add + * the include of your alternate allocator if needed (not needed in order + * to use the default libc allocator). */ + +#define s_malloc malloc +#define s_realloc realloc +#define s_free free diff --git a/ext/hiredis-vip-0.3.0/win32.h b/ext/hiredis-0.14.1/include/hiredis/win32.h similarity index 100% rename from ext/hiredis-vip-0.3.0/win32.h rename to ext/hiredis-0.14.1/include/hiredis/win32.h diff --git a/ext/hiredis-vip-0.3.0/.gitignore b/ext/hiredis-vip-0.3.0/.gitignore deleted file mode 100644 index c44b5c537..000000000 --- a/ext/hiredis-vip-0.3.0/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -/hiredis-test -/examples/hiredis-example* -/*.o -/*.so -/*.dylib -/*.a -/*.pc diff --git a/ext/hiredis-vip-0.3.0/.travis.yml b/ext/hiredis-vip-0.3.0/.travis.yml deleted file mode 100644 index 1df63b0b5..000000000 --- a/ext/hiredis-vip-0.3.0/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: c -compiler: - - gcc - - clang - -env: - - CFLAGS="-Werror" - - PRE="valgrind --track-origins=yes --leak-check=full" - - TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror" - - TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" - -install: - - sudo apt-get update -qq - - sudo apt-get install libc6-dbg libc6-dev libc6-i686:i386 libc6-dev-i386 libc6-dbg:i386 valgrind -y - -script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example diff --git a/ext/hiredis-vip-0.3.0/CHANGELOG.md b/ext/hiredis-vip-0.3.0/CHANGELOG.md deleted file mode 100644 index db304b6a7..000000000 --- a/ext/hiredis-vip-0.3.0/CHANGELOG.md +++ /dev/null @@ -1,16 +0,0 @@ -### 0.3.0 - Dec 07, 2016 - -* Support redisClustervCommand, redisClustervAppendCommand and redisClustervAsyncCommand api. (deep011) -* Add flags HIRCLUSTER_FLAG_ADD_OPENSLOT and HIRCLUSTER_FLAG_ROUTE_USE_SLOTS. (deep011) -* Support redisClusterCommandArgv related api. (deep011) -* Fix some serious bugs. (deep011) - -### 0.2.1 - Nov 24, 2015 - -This release support redis cluster api. - -* Add hiredis 0.3.1. (deep011) -* Support cluster synchronous API. (deep011) -* Support multi-key command(mget/mset/del) for redis cluster. (deep011) -* Support cluster pipelining. (deep011) -* Support cluster asynchronous API. (deep011) diff --git a/ext/hiredis-vip-0.3.0/COPYING b/ext/hiredis-vip-0.3.0/COPYING deleted file mode 100644 index a5fc97395..000000000 --- a/ext/hiredis-vip-0.3.0/COPYING +++ /dev/null @@ -1,29 +0,0 @@ -Copyright (c) 2009-2011, Salvatore Sanfilippo -Copyright (c) 2010-2011, Pieter Noordhuis - -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. diff --git a/ext/hiredis-vip-0.3.0/Makefile b/ext/hiredis-vip-0.3.0/Makefile deleted file mode 100644 index 58494bfc3..000000000 --- a/ext/hiredis-vip-0.3.0/Makefile +++ /dev/null @@ -1,205 +0,0 @@ -# Hiredis Makefile -# Copyright (C) 2010-2011 Salvatore Sanfilippo -# Copyright (C) 2010-2011 Pieter Noordhuis -# This file is released under the BSD license, see the COPYING file - -OBJ=net.o hiredis.o sds.o async.o read.o hiarray.o hiutil.o command.o crc16.o adlist.o hircluster.o -EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib -TESTS=hiredis-test -LIBNAME=libhiredis_vip -PKGCONFNAME=hiredis.pc - -HIREDIS_VIP_MAJOR=$(shell grep HIREDIS_VIP_MAJOR hircluster.h | awk '{print $$3}') -HIREDIS_VIP_MINOR=$(shell grep HIREDIS_VIP_MINOR hircluster.h | awk '{print $$3}') -HIREDIS_VIP_PATCH=$(shell grep HIREDIS_VIP_PATCH hircluster.h | awk '{print $$3}') - -# Installation related variables and target -PREFIX?=/usr/local -INCLUDE_PATH?=include/hiredis-vip -LIBRARY_PATH?=lib -PKGCONF_PATH?=pkgconfig -INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH) -INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH) -INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH) - -# redis-server configuration used for testing -REDIS_PORT=56379 -REDIS_SERVER=redis-server -define REDIS_TEST_CONFIG - daemonize yes - pidfile /tmp/hiredis-test-redis.pid - port $(REDIS_PORT) - bind 127.0.0.1 - unixsocket /tmp/hiredis-test-redis.sock -endef -export REDIS_TEST_CONFIG - -# Fallback to gcc when $CC is not in $PATH. -CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc') -OPTIMIZATION?=-O3 -WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -DEBUG?= -g -ggdb -REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) $(ARCH) -REAL_LDFLAGS=$(LDFLAGS) $(ARCH) - -DYLIBSUFFIX=so -STLIBSUFFIX=a -DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR) -DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR) -DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) -DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) -STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) -STLIB_MAKE_CMD=ar rcs $(STLIBNAME) - -# Platform-specific overrides -uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') -ifeq ($(uname_S),SunOS) - REAL_LDFLAGS+= -ldl -lnsl -lsocket - DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) - INSTALL= cp -r -endif -ifeq ($(uname_S),Darwin) - DYLIBSUFFIX=dylib - DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(DYLIBSUFFIX) - DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(DYLIBSUFFIX) - DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) -endif - -all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) - -# Deps (use make dep to generate this) - -adlist.o: adlist.c adlist.h hiutil.h -async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h -command.o: command.c command.h hiredis.h read.h sds.h adlist.h hiutil.h hiarray.h -crc16.o: crc16.c hiutil.h -dict.o: dict.c fmacros.h dict.h -hiarray.o: hiarray.c hiarray.h hiutil.h -hircluster.o: hircluster.c fmacros.h hircluster.h hiredis.h read.h sds.h adlist.h hiarray.h hiutil.h async.h command.h dict.c dict.h -hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h -hiutil.o: hiutil.c hiutil.h -net.o: net.c fmacros.h net.h hiredis.h read.h sds.h -read.o: read.c fmacros.h read.h sds.h -sds.o: sds.c sds.h -test.o: test.c fmacros.h hiredis.h read.h sds.h net.h - -$(DYLIBNAME): $(OBJ) - $(DYLIB_MAKE_CMD) $(OBJ) - -$(STLIBNAME): $(OBJ) - $(STLIB_MAKE_CMD) $(OBJ) - -dynamic: $(DYLIBNAME) -static: $(STLIBNAME) - -# Binaries: -hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) - -hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) - -hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME) - -ifndef AE_DIR -hiredis-example-ae: - @echo "Please specify AE_DIR (e.g. /src)" - @false -else -hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME) -endif - -ifndef LIBUV_DIR -hiredis-example-libuv: - @echo "Please specify LIBUV_DIR (e.g. ../libuv/)" - @false -else -hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread $(STLIBNAME) -endif - -hiredis-example: examples/example.c $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) - -examples: $(EXAMPLES) - -hiredis-test: test.o $(STLIBNAME) - -hiredis-%: %.o $(STLIBNAME) - $(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) - -test: hiredis-test - ./hiredis-test - -check: hiredis-test - @echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) - - $(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \ - ( kill `cat /tmp/hiredis-test-redis.pid` && false ) - kill `cat /tmp/hiredis-test-redis.pid` - -.c.o: - $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< - -clean: - rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov - -dep: - $(CC) -MM *.c - -ifeq ($(uname_S),SunOS) - INSTALL?= cp -r -endif - -INSTALL?= cp -a - -$(PKGCONFNAME): hiredis.h - @echo "Generating $@ for pkgconfig..." - @echo prefix=$(PREFIX) > $@ - @echo exec_prefix=\$${prefix} >> $@ - @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@ - @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@ - @echo >> $@ - @echo Name: hiredis >> $@ - @echo Description: Minimalistic C client library for Redis. >> $@ - @echo Version: $(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(HIREDIS_VIP_PATCH) >> $@ - @echo Libs: -L\$${libdir} -lhiredis >> $@ - @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ - -install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) - mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) - $(INSTALL) hiredis.h async.h read.h sds.h hiutil.h hiarray.h dict.h dict.c adlist.h fmacros.h hircluster.h adapters $(INSTALL_INCLUDE_PATH) - $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) - cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME) - cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MAJOR_NAME) $(DYLIBNAME) - $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) - mkdir -p $(INSTALL_PKGCONF_PATH) - $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH) - -32bit: - @echo "" - @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386" - @echo "" - $(MAKE) CFLAGS="-m32" LDFLAGS="-m32" - -32bit-vars: - $(eval CFLAGS=-m32) - $(eval LDFLAGS=-m32) - -gprof: - $(MAKE) CFLAGS="-pg" LDFLAGS="-pg" - -gcov: - $(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" - -coverage: gcov - make check - mkdir -p tmp/lcov - lcov -d . -c -o tmp/lcov/hiredis.info - genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info - -noopt: - $(MAKE) OPTIMIZATION="" - -.PHONY: all test check clean dep install 32bit gprof gcov noopt diff --git a/ext/hiredis-vip-0.3.0/README.md b/ext/hiredis-vip-0.3.0/README.md deleted file mode 100644 index 897419390..000000000 --- a/ext/hiredis-vip-0.3.0/README.md +++ /dev/null @@ -1,255 +0,0 @@ - -# HIREDIS-VIP - -Hiredis-vip is a C client library for the [Redis](http://redis.io/) database. - -Hiredis-vip supported redis cluster. - -Hiredis-vip fully contained and based on [Hiredis](https://github.com/redis/hiredis) . - -## CLUSTER SUPPORT - -### FEATURES: - -* **`SUPPORT REDIS CLUSTER`**: - * Connect to redis cluster and run commands. - -* **`SUPPORT MULTI-KEY COMMAND`**: - * Support `MSET`, `MGET` and `DEL`. - -* **`SUPPORT PIPELING`**: - * Support redis pipeline and can contain multi-key command like above. - -* **`SUPPORT Asynchronous API`**: - * User can run commands with asynchronous mode. - -### CLUSTER API: - -```c -redisClusterContext *redisClusterConnect(const char *addrs, int flags); -redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv, int flags); -redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); -void redisClusterFree(redisClusterContext *cc); -void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); -void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len); -void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap); -void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); -void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); -redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); -int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len); -int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap); -int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); -int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); -int redisClusterGetReply(redisClusterContext *cc, void **reply); -void redisClusterReset(redisClusterContext *cc); - -redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); -int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); -int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); -int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len); -int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap); -int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...); -int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); - -void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); -void redisClusterAsyncFree(redisClusterAsyncContext *acc); -``` - -## Quick usage - -If you want used but not read the follow, please reference the examples: -https://github.com/vipshop/hiredis-vip/wiki - -## Cluster synchronous API - -To consume the synchronous API, there are only a few function calls that need to be introduced: - -```c -redisClusterContext *redisClusterConnect(const char *addrs, int flags); -void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); -void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); -void redisClusterFree(redisClusterContext *cc); -``` - -### Cluster connecting - -The function `redisClusterConnect` is used to create a so-called `redisClusterContext`. The -context is where Hiredis-vip Cluster holds state for connections. The `redisClusterContext` -struct has an integer `err` field that is non-zero when the connection is in -an error state. The field `errstr` will contain a string with a description of -the error. -After trying to connect to Redis using `redisClusterContext` you should -check the `err` field to see if establishing the connection was successful: -```c -redisClusterContext *cc = redisClusterConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); -if (cc != NULL && cc->err) { - printf("Error: %s\n", cc->errstr); - // handle error -} -``` - -### Cluster sending commands - -The next that will be introduced is `redisClusterCommand`. -This function takes a format similar to printf. In the simplest form, -it is used like this: -```c -reply = redisClusterCommand(clustercontext, "SET foo bar"); -``` - -The specifier `%s` interpolates a string in the command, and uses `strlen` to -determine the length of the string: -```c -reply = redisClusterCommand(clustercontext, "SET foo %s", value); -``` -Internally, Hiredis-vip splits the command in different arguments and will -convert it to the protocol used to communicate with Redis. -One or more spaces separates arguments, so you can use the specifiers -anywhere in an argument: -```c -reply = redisClusterCommand(clustercontext, "SET key:%s %s", myid, value); -``` - -### Cluster multi-key commands - -Hiredis-vip supports mget/mset/del multi-key commands. -Those multi-key commands is highly effective. -Millions of keys in one mget command just used several seconds. - -Example: -```c -reply = redisClusterCommand(clustercontext, "mget %s %s %s %s", key1, key2, key3, key4); -``` - -### Cluster cleaning up - -To disconnect and free the context the following function can be used: -```c -void redisClusterFree(redisClusterContext *cc); -``` -This function immediately closes the socket and then frees the allocations done in -creating the context. - -### Cluster pipelining - -The function `redisClusterGetReply` is exported as part of the Hiredis API and can be used -when a reply is expected on the socket. To pipeline commands, the only things that needs -to be done is filling up the output buffer. For this cause, two commands can be used that -are identical to the `redisClusterCommand` family, apart from not returning a reply: -```c -int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); -int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv); -``` -After calling either function one or more times, `redisClusterGetReply` can be used to receive the -subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where -the latter means an error occurred while reading a reply. Just as with the other commands, -the `err` field in the context can be used to find out what the cause of this error is. -```c -void redisClusterReset(redisClusterContext *cc); -``` -Warning: You must call `redisClusterReset` function after one pipelining anyway. - -The following examples shows a simple cluster pipeline: -```c -redisReply *reply; -redisClusterAppendCommand(clusterContext,"SET foo bar"); -redisClusterAppendCommand(clusterContext,"GET foo"); -redisClusterGetReply(clusterContext,&reply); // reply for SET -freeReplyObject(reply); -redisClusterGetReply(clusterContext,&reply); // reply for GET -freeReplyObject(reply); -redisClusterReset(clusterContext); -``` - -## Cluster asynchronous API - -Hiredis-vip comes with an cluster asynchronous API that works easily with any event library. -Now we just support and test for libevent and redis ae, if you need for other event libraries, -please contact with us, and we will support it quickly. - -### Connecting - -The function `redisAsyncConnect` can be used to establish a non-blocking connection to -Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field -should be checked after creation to see if there were errors creating the connection. -Because the connection that will be created is non-blocking, the kernel is not able to -instantly return if the specified host and port is able to accept a connection. -```c -redisClusterAsyncContext *acc = redisClusterAsyncConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); -if (acc->err) { - printf("Error: %s\n", acc->errstr); - // handle error -} -``` - -The cluster asynchronous context can hold a disconnect callback function that is called when the -connection is disconnected (either because of an error or per user request). This function should -have the following prototype: -```c -void(const redisAsyncContext *c, int status); -``` -On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the -user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err` -field in the context can be accessed to find out the cause of the error. - -You not need to reconnect in the disconnect callback, hiredis-vip will reconnect this connection itself -when commands come to this redis node. - -Setting the disconnect callback can only be done once per context. For subsequent calls it will -return `REDIS_ERR`. The function to set the disconnect callback has the following prototype: -```c -int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); -``` -### Sending commands and their callbacks - -In an cluster asynchronous context, commands are automatically pipelined due to the nature of an event loop. -Therefore, unlike the cluster synchronous API, there is only a single way to send commands. -Because commands are sent to Redis cluster asynchronously, issuing a command requires a callback function -that is called when the reply is received. Reply callbacks should have the following prototype: -```c -void(redisClusterAsyncContext *acc, void *reply, void *privdata); -``` -The `privdata` argument can be used to curry arbitrary data to the callback from the point where -the command is initially queued for execution. - -The functions that can be used to issue commands in an asynchronous context are: -```c -int redisClusterAsyncCommand( - redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, - void *privdata, const char *format, ...); -``` -This function work like their blocking counterparts. The return value is `REDIS_OK` when the command -was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection -is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is -returned on calls to the `redisClusterAsyncCommand` family. - -If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback -for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only -valid for the duration of the callback. - -All pending callbacks are called with a `NULL` reply when the context encountered an error. - -### Disconnecting - -An cluster asynchronous connection can be terminated using: -```c -void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); -``` -When this function is called, the connection is **not** immediately terminated. Instead, new -commands are no longer accepted and the connection is only terminated when all pending commands -have been written to the socket, their respective replies have been read and their respective -callbacks have been executed. After this, the disconnection callback is executed with the -`REDIS_OK` status and the context object is freed. - -### Hooking it up to event library *X* - -There are a few hooks that need to be set on the cluster context object after it is created. -See the `adapters/` directory for bindings to *ae* and *libevent*. - -## AUTHORS - -Hiredis-vip was maintained and used at vipshop(https://github.com/vipshop). -The redis client library part in hiredis-vip is same as hiredis(https://github.com/redis/hiredis). -The redis cluster client library part in hiredis-vip is written by deep(https://github.com/deep011). -Hiredis-vip is released under the BSD license. diff --git a/ext/hiredis-vip-0.3.0/adapters/ae.h b/ext/hiredis-vip-0.3.0/adapters/ae.h deleted file mode 100644 index f861cf287..000000000 --- a/ext/hiredis-vip-0.3.0/adapters/ae.h +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * 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. - */ - -#ifndef __HIREDIS_AE_H__ -#define __HIREDIS_AE_H__ -#include -#include -#include "../hiredis.h" -#include "../async.h" - -#if 1 //shenzheng 2015-11-5 redis cluster -#include "../hircluster.h" -#endif //shenzheng 2015-11-5 redis cluster - -typedef struct redisAeEvents { - redisAsyncContext *context; - aeEventLoop *loop; - int fd; - int reading, writing; -} redisAeEvents; - -static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { - ((void)el); ((void)fd); ((void)mask); - - redisAeEvents *e = (redisAeEvents*)privdata; - redisAsyncHandleRead(e->context); -} - -static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { - ((void)el); ((void)fd); ((void)mask); - - redisAeEvents *e = (redisAeEvents*)privdata; - redisAsyncHandleWrite(e->context); -} - -static void redisAeAddRead(void *privdata) { - redisAeEvents *e = (redisAeEvents*)privdata; - aeEventLoop *loop = e->loop; - if (!e->reading) { - e->reading = 1; - aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e); - } -} - -static void redisAeDelRead(void *privdata) { - redisAeEvents *e = (redisAeEvents*)privdata; - aeEventLoop *loop = e->loop; - if (e->reading) { - e->reading = 0; - aeDeleteFileEvent(loop,e->fd,AE_READABLE); - } -} - -static void redisAeAddWrite(void *privdata) { - redisAeEvents *e = (redisAeEvents*)privdata; - aeEventLoop *loop = e->loop; - if (!e->writing) { - e->writing = 1; - aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e); - } -} - -static void redisAeDelWrite(void *privdata) { - redisAeEvents *e = (redisAeEvents*)privdata; - aeEventLoop *loop = e->loop; - if (e->writing) { - e->writing = 0; - aeDeleteFileEvent(loop,e->fd,AE_WRITABLE); - } -} - -static void redisAeCleanup(void *privdata) { - redisAeEvents *e = (redisAeEvents*)privdata; - redisAeDelRead(privdata); - redisAeDelWrite(privdata); - free(e); -} - -static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisAeEvents *e; - - /* Nothing should be attached when something is already attached */ - if (ac->ev.data != NULL) - return REDIS_ERR; - - /* Create container for context and r/w events */ - e = (redisAeEvents*)malloc(sizeof(*e)); - e->context = ac; - e->loop = loop; - e->fd = c->fd; - e->reading = e->writing = 0; - - /* Register functions to start/stop listening for events */ - ac->ev.addRead = redisAeAddRead; - ac->ev.delRead = redisAeDelRead; - ac->ev.addWrite = redisAeAddWrite; - ac->ev.delWrite = redisAeDelWrite; - ac->ev.cleanup = redisAeCleanup; - ac->ev.data = e; - - return REDIS_OK; -} - -#if 1 //shenzheng 2015-11-5 redis cluster - -static int redisAeAttach_link(redisAsyncContext *ac, void *base) -{ - redisAeAttach((aeEventLoop *)base, ac); -} - -static int redisClusterAeAttach(aeEventLoop *loop, redisClusterAsyncContext *acc) { - - if(acc == NULL || loop == NULL) - { - return REDIS_ERR; - } - - acc->adapter = loop; - acc->attach_fn = redisAeAttach_link; - - return REDIS_OK; -} - -#endif //shenzheng 2015-11-5 redis cluster - -#endif diff --git a/ext/hiredis-vip-0.3.0/adapters/glib.h b/ext/hiredis-vip-0.3.0/adapters/glib.h deleted file mode 100644 index e13eee73b..000000000 --- a/ext/hiredis-vip-0.3.0/adapters/glib.h +++ /dev/null @@ -1,153 +0,0 @@ -#ifndef __HIREDIS_GLIB_H__ -#define __HIREDIS_GLIB_H__ - -#include - -#include "../hiredis.h" -#include "../async.h" - -typedef struct -{ - GSource source; - redisAsyncContext *ac; - GPollFD poll_fd; -} RedisSource; - -static void -redis_source_add_read (gpointer data) -{ - RedisSource *source = data; - g_return_if_fail(source); - source->poll_fd.events |= G_IO_IN; - g_main_context_wakeup(g_source_get_context(data)); -} - -static void -redis_source_del_read (gpointer data) -{ - RedisSource *source = data; - g_return_if_fail(source); - source->poll_fd.events &= ~G_IO_IN; - g_main_context_wakeup(g_source_get_context(data)); -} - -static void -redis_source_add_write (gpointer data) -{ - RedisSource *source = data; - g_return_if_fail(source); - source->poll_fd.events |= G_IO_OUT; - g_main_context_wakeup(g_source_get_context(data)); -} - -static void -redis_source_del_write (gpointer data) -{ - RedisSource *source = data; - g_return_if_fail(source); - source->poll_fd.events &= ~G_IO_OUT; - g_main_context_wakeup(g_source_get_context(data)); -} - -static void -redis_source_cleanup (gpointer data) -{ - RedisSource *source = data; - - g_return_if_fail(source); - - redis_source_del_read(source); - redis_source_del_write(source); - /* - * It is not our responsibility to remove ourself from the - * current main loop. However, we will remove the GPollFD. - */ - if (source->poll_fd.fd >= 0) { - g_source_remove_poll(data, &source->poll_fd); - source->poll_fd.fd = -1; - } -} - -static gboolean -redis_source_prepare (GSource *source, - gint *timeout_) -{ - RedisSource *redis = (RedisSource *)source; - *timeout_ = -1; - return !!(redis->poll_fd.events & redis->poll_fd.revents); -} - -static gboolean -redis_source_check (GSource *source) -{ - RedisSource *redis = (RedisSource *)source; - return !!(redis->poll_fd.events & redis->poll_fd.revents); -} - -static gboolean -redis_source_dispatch (GSource *source, - GSourceFunc callback, - gpointer user_data) -{ - RedisSource *redis = (RedisSource *)source; - - if ((redis->poll_fd.revents & G_IO_OUT)) { - redisAsyncHandleWrite(redis->ac); - redis->poll_fd.revents &= ~G_IO_OUT; - } - - if ((redis->poll_fd.revents & G_IO_IN)) { - redisAsyncHandleRead(redis->ac); - redis->poll_fd.revents &= ~G_IO_IN; - } - - if (callback) { - return callback(user_data); - } - - return TRUE; -} - -static void -redis_source_finalize (GSource *source) -{ - RedisSource *redis = (RedisSource *)source; - - if (redis->poll_fd.fd >= 0) { - g_source_remove_poll(source, &redis->poll_fd); - redis->poll_fd.fd = -1; - } -} - -static GSource * -redis_source_new (redisAsyncContext *ac) -{ - static GSourceFuncs source_funcs = { - .prepare = redis_source_prepare, - .check = redis_source_check, - .dispatch = redis_source_dispatch, - .finalize = redis_source_finalize, - }; - redisContext *c = &ac->c; - RedisSource *source; - - g_return_val_if_fail(ac != NULL, NULL); - - source = (RedisSource *)g_source_new(&source_funcs, sizeof *source); - source->ac = ac; - source->poll_fd.fd = c->fd; - source->poll_fd.events = 0; - source->poll_fd.revents = 0; - g_source_add_poll((GSource *)source, &source->poll_fd); - - ac->ev.addRead = redis_source_add_read; - ac->ev.delRead = redis_source_del_read; - ac->ev.addWrite = redis_source_add_write; - ac->ev.delWrite = redis_source_del_write; - ac->ev.cleanup = redis_source_cleanup; - ac->ev.data = source; - - return (GSource *)source; -} - -#endif /* __HIREDIS_GLIB_H__ */ diff --git a/ext/hiredis-vip-0.3.0/adapters/libev.h b/ext/hiredis-vip-0.3.0/adapters/libev.h deleted file mode 100644 index 2bf8d521f..000000000 --- a/ext/hiredis-vip-0.3.0/adapters/libev.h +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * 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. - */ - -#ifndef __HIREDIS_LIBEV_H__ -#define __HIREDIS_LIBEV_H__ -#include -#include -#include -#include "../hiredis.h" -#include "../async.h" - -typedef struct redisLibevEvents { - redisAsyncContext *context; - struct ev_loop *loop; - int reading, writing; - ev_io rev, wev; -} redisLibevEvents; - -static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { -#if EV_MULTIPLICITY - ((void)loop); -#endif - ((void)revents); - - redisLibevEvents *e = (redisLibevEvents*)watcher->data; - redisAsyncHandleRead(e->context); -} - -static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { -#if EV_MULTIPLICITY - ((void)loop); -#endif - ((void)revents); - - redisLibevEvents *e = (redisLibevEvents*)watcher->data; - redisAsyncHandleWrite(e->context); -} - -static void redisLibevAddRead(void *privdata) { - redisLibevEvents *e = (redisLibevEvents*)privdata; - struct ev_loop *loop = e->loop; - ((void)loop); - if (!e->reading) { - e->reading = 1; - ev_io_start(EV_A_ &e->rev); - } -} - -static void redisLibevDelRead(void *privdata) { - redisLibevEvents *e = (redisLibevEvents*)privdata; - struct ev_loop *loop = e->loop; - ((void)loop); - if (e->reading) { - e->reading = 0; - ev_io_stop(EV_A_ &e->rev); - } -} - -static void redisLibevAddWrite(void *privdata) { - redisLibevEvents *e = (redisLibevEvents*)privdata; - struct ev_loop *loop = e->loop; - ((void)loop); - if (!e->writing) { - e->writing = 1; - ev_io_start(EV_A_ &e->wev); - } -} - -static void redisLibevDelWrite(void *privdata) { - redisLibevEvents *e = (redisLibevEvents*)privdata; - struct ev_loop *loop = e->loop; - ((void)loop); - if (e->writing) { - e->writing = 0; - ev_io_stop(EV_A_ &e->wev); - } -} - -static void redisLibevCleanup(void *privdata) { - redisLibevEvents *e = (redisLibevEvents*)privdata; - redisLibevDelRead(privdata); - redisLibevDelWrite(privdata); - free(e); -} - -static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisLibevEvents *e; - - /* Nothing should be attached when something is already attached */ - if (ac->ev.data != NULL) - return REDIS_ERR; - - /* Create container for context and r/w events */ - e = (redisLibevEvents*)malloc(sizeof(*e)); - e->context = ac; -#if EV_MULTIPLICITY - e->loop = loop; -#else - e->loop = NULL; -#endif - e->reading = e->writing = 0; - e->rev.data = e; - e->wev.data = e; - - /* Register functions to start/stop listening for events */ - ac->ev.addRead = redisLibevAddRead; - ac->ev.delRead = redisLibevDelRead; - ac->ev.addWrite = redisLibevAddWrite; - ac->ev.delWrite = redisLibevDelWrite; - ac->ev.cleanup = redisLibevCleanup; - ac->ev.data = e; - - /* Initialize read/write events */ - ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ); - ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE); - return REDIS_OK; -} - -#endif diff --git a/ext/hiredis-vip-0.3.0/adapters/libevent.h b/ext/hiredis-vip-0.3.0/adapters/libevent.h deleted file mode 100644 index 6bc911c77..000000000 --- a/ext/hiredis-vip-0.3.0/adapters/libevent.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * 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. - */ - -#ifndef __HIREDIS_LIBEVENT_H__ -#define __HIREDIS_LIBEVENT_H__ -#include -#include "../hiredis.h" -#include "../async.h" - -#if 1 //shenzheng 2015-9-21 redis cluster -#include "../hircluster.h" -#endif //shenzheng 2015-9-21 redis cluster - -typedef struct redisLibeventEvents { - redisAsyncContext *context; - struct event rev, wev; -} redisLibeventEvents; - -static void redisLibeventReadEvent(int fd, short event, void *arg) { - ((void)fd); ((void)event); - redisLibeventEvents *e = (redisLibeventEvents*)arg; - redisAsyncHandleRead(e->context); -} - -static void redisLibeventWriteEvent(int fd, short event, void *arg) { - ((void)fd); ((void)event); - redisLibeventEvents *e = (redisLibeventEvents*)arg; - redisAsyncHandleWrite(e->context); -} - -static void redisLibeventAddRead(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_add(&e->rev,NULL); -} - -static void redisLibeventDelRead(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_del(&e->rev); -} - -static void redisLibeventAddWrite(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_add(&e->wev,NULL); -} - -static void redisLibeventDelWrite(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_del(&e->wev); -} - -static void redisLibeventCleanup(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_del(&e->rev); - event_del(&e->wev); - free(e); -} - -static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { - redisContext *c = &(ac->c); - redisLibeventEvents *e; - - /* Nothing should be attached when something is already attached */ - if (ac->ev.data != NULL) - return REDIS_ERR; - - /* Create container for context and r/w events */ - e = (redisLibeventEvents*)malloc(sizeof(*e)); - e->context = ac; - - /* Register functions to start/stop listening for events */ - ac->ev.addRead = redisLibeventAddRead; - ac->ev.delRead = redisLibeventDelRead; - ac->ev.addWrite = redisLibeventAddWrite; - ac->ev.delWrite = redisLibeventDelWrite; - ac->ev.cleanup = redisLibeventCleanup; - ac->ev.data = e; - - /* Initialize and install read/write events */ - event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e); - event_set(&e->wev,c->fd,EV_WRITE,redisLibeventWriteEvent,e); - event_base_set(base,&e->rev); - event_base_set(base,&e->wev); - return REDIS_OK; -} - -#if 1 //shenzheng 2015-9-21 redis cluster - -static int redisLibeventAttach_link(redisAsyncContext *ac, void *base) -{ - redisLibeventAttach(ac, (struct event_base *)base); -} - -static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct event_base *base) { - - if(acc == NULL || base == NULL) - { - return REDIS_ERR; - } - - acc->adapter = base; - acc->attach_fn = redisLibeventAttach_link; - - return REDIS_OK; -} - -#endif //shenzheng 2015-9-21 redis cluster - -#endif diff --git a/ext/hiredis-vip-0.3.0/adapters/libuv.h b/ext/hiredis-vip-0.3.0/adapters/libuv.h deleted file mode 100644 index 3c9a49f53..000000000 --- a/ext/hiredis-vip-0.3.0/adapters/libuv.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef __HIREDIS_LIBUV_H__ -#define __HIREDIS_LIBUV_H__ -#include -#include -#include "../hiredis.h" -#include "../async.h" -#include - -typedef struct redisLibuvEvents { - redisAsyncContext* context; - uv_poll_t handle; - int events; -} redisLibuvEvents; - - -static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { - redisLibuvEvents* p = (redisLibuvEvents*)handle->data; - - if (status != 0) { - return; - } - - if (events & UV_READABLE) { - redisAsyncHandleRead(p->context); - } - if (events & UV_WRITABLE) { - redisAsyncHandleWrite(p->context); - } -} - - -static void redisLibuvAddRead(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; - - p->events |= UV_READABLE; - - uv_poll_start(&p->handle, p->events, redisLibuvPoll); -} - - -static void redisLibuvDelRead(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; - - p->events &= ~UV_READABLE; - - if (p->events) { - uv_poll_start(&p->handle, p->events, redisLibuvPoll); - } else { - uv_poll_stop(&p->handle); - } -} - - -static void redisLibuvAddWrite(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; - - p->events |= UV_WRITABLE; - - uv_poll_start(&p->handle, p->events, redisLibuvPoll); -} - - -static void redisLibuvDelWrite(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; - - p->events &= ~UV_WRITABLE; - - if (p->events) { - uv_poll_start(&p->handle, p->events, redisLibuvPoll); - } else { - uv_poll_stop(&p->handle); - } -} - - -static void on_close(uv_handle_t* handle) { - redisLibuvEvents* p = (redisLibuvEvents*)handle->data; - - free(p); -} - - -static void redisLibuvCleanup(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; - - uv_close((uv_handle_t*)&p->handle, on_close); -} - - -static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { - redisContext *c = &(ac->c); - - if (ac->ev.data != NULL) { - return REDIS_ERR; - } - - ac->ev.addRead = redisLibuvAddRead; - ac->ev.delRead = redisLibuvDelRead; - ac->ev.addWrite = redisLibuvAddWrite; - ac->ev.delWrite = redisLibuvDelWrite; - ac->ev.cleanup = redisLibuvCleanup; - - redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p)); - - if (!p) { - return REDIS_ERR; - } - - memset(p, 0, sizeof(*p)); - - if (uv_poll_init(loop, &p->handle, c->fd) != 0) { - return REDIS_ERR; - } - - ac->ev.data = p; - p->handle.data = p; - p->context = ac; - - return REDIS_OK; -} - -#endif diff --git a/ext/hiredis-vip-0.3.0/adlist.c b/ext/hiredis-vip-0.3.0/adlist.c deleted file mode 100644 index b490a6bd1..000000000 --- a/ext/hiredis-vip-0.3.0/adlist.c +++ /dev/null @@ -1,341 +0,0 @@ -/* adlist.c - A generic doubly linked list implementation - * - * Copyright (c) 2006-2010, Salvatore Sanfilippo - * 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 -#include "adlist.h" -#include "hiutil.h" - -/* Create a new list. The created list can be freed with - * AlFreeList(), but private value of every node need to be freed - * by the user before to call AlFreeList(). - * - * On error, NULL is returned. Otherwise the pointer to the new list. */ -hilist *listCreate(void) -{ - struct hilist *list; - - if ((list = hi_alloc(sizeof(*list))) == NULL) - return NULL; - list->head = list->tail = NULL; - list->len = 0; - list->dup = NULL; - list->free = NULL; - list->match = NULL; - return list; -} - -/* Free the whole list. - * - * This function can't fail. */ -void listRelease(hilist *list) -{ - unsigned long len; - listNode *current, *next; - - current = list->head; - len = list->len; - while(len--) { - next = current->next; - if (list->free) list->free(current->value); - hi_free(current); - current = next; - } - hi_free(list); -} - -/* Add a new node to the list, to head, containing the specified 'value' - * pointer as value. - * - * On error, NULL is returned and no operation is performed (i.e. the - * list remains unaltered). - * On success the 'list' pointer you pass to the function is returned. */ -hilist *listAddNodeHead(hilist *list, void *value) -{ - listNode *node; - - if ((node = hi_alloc(sizeof(*node))) == NULL) - return NULL; - node->value = value; - if (list->len == 0) { - list->head = list->tail = node; - node->prev = node->next = NULL; - } else { - node->prev = NULL; - node->next = list->head; - list->head->prev = node; - list->head = node; - } - list->len++; - return list; -} - -/* Add a new node to the list, to tail, containing the specified 'value' - * pointer as value. - * - * On error, NULL is returned and no operation is performed (i.e. the - * list remains unaltered). - * On success the 'list' pointer you pass to the function is returned. */ -hilist *listAddNodeTail(hilist *list, void *value) -{ - listNode *node; - - if ((node = hi_alloc(sizeof(*node))) == NULL) - return NULL; - node->value = value; - if (list->len == 0) { - list->head = list->tail = node; - node->prev = node->next = NULL; - } else { - node->prev = list->tail; - node->next = NULL; - list->tail->next = node; - list->tail = node; - } - list->len++; - return list; -} - -hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after) { - listNode *node; - - if ((node = hi_alloc(sizeof(*node))) == NULL) - return NULL; - node->value = value; - if (after) { - node->prev = old_node; - node->next = old_node->next; - if (list->tail == old_node) { - list->tail = node; - } - } else { - node->next = old_node; - node->prev = old_node->prev; - if (list->head == old_node) { - list->head = node; - } - } - if (node->prev != NULL) { - node->prev->next = node; - } - if (node->next != NULL) { - node->next->prev = node; - } - list->len++; - return list; -} - -/* Remove the specified node from the specified list. - * It's up to the caller to free the private value of the node. - * - * This function can't fail. */ -void listDelNode(hilist *list, listNode *node) -{ - if (node->prev) - node->prev->next = node->next; - else - list->head = node->next; - if (node->next) - node->next->prev = node->prev; - else - list->tail = node->prev; - if (list->free) list->free(node->value); - hi_free(node); - list->len--; -} - -/* Returns a list iterator 'iter'. After the initialization every - * call to listNext() will return the next element of the list. - * - * This function can't fail. */ -listIter *listGetIterator(hilist *list, int direction) -{ - listIter *iter; - - if ((iter = hi_alloc(sizeof(*iter))) == NULL) return NULL; - if (direction == AL_START_HEAD) - iter->next = list->head; - else - iter->next = list->tail; - iter->direction = direction; - return iter; -} - -/* Release the iterator memory */ -void listReleaseIterator(listIter *iter) { - hi_free(iter); -} - -/* Create an iterator in the list private iterator structure */ -void listRewind(hilist *list, listIter *li) { - li->next = list->head; - li->direction = AL_START_HEAD; -} - -void listRewindTail(hilist *list, listIter *li) { - li->next = list->tail; - li->direction = AL_START_TAIL; -} - -/* Return the next element of an iterator. - * It's valid to remove the currently returned element using - * listDelNode(), but not to remove other elements. - * - * The function returns a pointer to the next element of the list, - * or NULL if there are no more elements, so the classical usage patter - * is: - * - * iter = listGetIterator(list,); - * while ((node = listNext(iter)) != NULL) { - * doSomethingWith(listNodeValue(node)); - * } - * - * */ -listNode *listNext(listIter *iter) -{ - listNode *current = iter->next; - - if (current != NULL) { - if (iter->direction == AL_START_HEAD) - iter->next = current->next; - else - iter->next = current->prev; - } - return current; -} - -/* Duplicate the whole list. On out of memory NULL is returned. - * On success a copy of the original list is returned. - * - * The 'Dup' method set with listSetDupMethod() function is used - * to copy the node value. Otherwise the same pointer value of - * the original node is used as value of the copied node. - * - * The original list both on success or error is never modified. */ -hilist *listDup(hilist *orig) -{ - hilist *copy; - listIter *iter; - listNode *node; - - if ((copy = listCreate()) == NULL) - return NULL; - copy->dup = orig->dup; - copy->free = orig->free; - copy->match = orig->match; - iter = listGetIterator(orig, AL_START_HEAD); - while((node = listNext(iter)) != NULL) { - void *value; - - if (copy->dup) { - value = copy->dup(node->value); - if (value == NULL) { - listRelease(copy); - listReleaseIterator(iter); - return NULL; - } - } else - value = node->value; - if (listAddNodeTail(copy, value) == NULL) { - listRelease(copy); - listReleaseIterator(iter); - return NULL; - } - } - listReleaseIterator(iter); - return copy; -} - -/* Search the list for a node matching a given key. - * The match is performed using the 'match' method - * set with listSetMatchMethod(). If no 'match' method - * is set, the 'value' pointer of every node is directly - * compared with the 'key' pointer. - * - * On success the first matching node pointer is returned - * (search starts from head). If no matching node exists - * NULL is returned. */ -listNode *listSearchKey(hilist *list, void *key) -{ - listIter *iter; - listNode *node; - - iter = listGetIterator(list, AL_START_HEAD); - while((node = listNext(iter)) != NULL) { - if (list->match) { - if (list->match(node->value, key)) { - listReleaseIterator(iter); - return node; - } - } else { - if (key == node->value) { - listReleaseIterator(iter); - return node; - } - } - } - listReleaseIterator(iter); - return NULL; -} - -/* Return the element at the specified zero-based index - * where 0 is the head, 1 is the element next to head - * and so on. Negative integers are used in order to count - * from the tail, -1 is the last element, -2 the penultimate - * and so on. If the index is out of range NULL is returned. */ -listNode *listIndex(hilist *list, long index) { - listNode *n; - - if (index < 0) { - index = (-index)-1; - n = list->tail; - while(index-- && n) n = n->prev; - } else { - n = list->head; - while(index-- && n) n = n->next; - } - return n; -} - -/* Rotate the list removing the tail node and inserting it to the head. */ -void listRotate(hilist *list) { - listNode *tail = list->tail; - - if (listLength(list) <= 1) return; - - /* Detach current tail */ - list->tail = tail->prev; - list->tail->next = NULL; - /* Move it as head */ - list->head->prev = tail; - tail->prev = NULL; - tail->next = list->head; - list->head = tail; -} diff --git a/ext/hiredis-vip-0.3.0/adlist.h b/ext/hiredis-vip-0.3.0/adlist.h deleted file mode 100644 index 5b9a53ea5..000000000 --- a/ext/hiredis-vip-0.3.0/adlist.h +++ /dev/null @@ -1,93 +0,0 @@ -/* adlist.h - A generic doubly linked list implementation - * - * Copyright (c) 2006-2012, Salvatore Sanfilippo - * 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. - */ - -#ifndef __ADLIST_H__ -#define __ADLIST_H__ - -/* Node, List, and Iterator are the only data structures used currently. */ - -typedef struct listNode { - struct listNode *prev; - struct listNode *next; - void *value; -} listNode; - -typedef struct listIter { - listNode *next; - int direction; -} listIter; - -typedef struct hilist { - listNode *head; - listNode *tail; - void *(*dup)(void *ptr); - void (*free)(void *ptr); - int (*match)(void *ptr, void *key); - unsigned long len; -} hilist; - -/* Functions implemented as macros */ -#define listLength(l) ((l)->len) -#define listFirst(l) ((l)->head) -#define listLast(l) ((l)->tail) -#define listPrevNode(n) ((n)->prev) -#define listNextNode(n) ((n)->next) -#define listNodeValue(n) ((n)->value) - -#define listSetDupMethod(l,m) ((l)->dup = (m)) -#define listSetFreeMethod(l,m) ((l)->free = (m)) -#define listSetMatchMethod(l,m) ((l)->match = (m)) - -#define listGetDupMethod(l) ((l)->dup) -#define listGetFree(l) ((l)->free) -#define listGetMatchMethod(l) ((l)->match) - -/* Prototypes */ -hilist *listCreate(void); -void listRelease(hilist *list); -hilist *listAddNodeHead(hilist *list, void *value); -hilist *listAddNodeTail(hilist *list, void *value); -hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after); -void listDelNode(hilist *list, listNode *node); -listIter *listGetIterator(hilist *list, int direction); -listNode *listNext(listIter *iter); -void listReleaseIterator(listIter *iter); -hilist *listDup(hilist *orig); -listNode *listSearchKey(hilist *list, void *key); -listNode *listIndex(hilist *list, long index); -void listRewind(hilist *list, listIter *li); -void listRewindTail(hilist *list, listIter *li); -void listRotate(hilist *list); - -/* Directions for iterators */ -#define AL_START_HEAD 0 -#define AL_START_TAIL 1 - -#endif /* __ADLIST_H__ */ diff --git a/ext/hiredis-vip-0.3.0/async.c b/ext/hiredis-vip-0.3.0/async.c deleted file mode 100644 index 75a3575de..000000000 --- a/ext/hiredis-vip-0.3.0/async.c +++ /dev/null @@ -1,691 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * 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 "fmacros.h" -#include -#include -#include -#include -#include -#include -#include "async.h" -#include "net.h" -#include "dict.c" -#include "sds.h" - -#define _EL_ADD_READ(ctx) do { \ - if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ - } while(0) -#define _EL_DEL_READ(ctx) do { \ - if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ - } while(0) -#define _EL_ADD_WRITE(ctx) do { \ - if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ - } while(0) -#define _EL_DEL_WRITE(ctx) do { \ - if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ - } while(0) -#define _EL_CLEANUP(ctx) do { \ - if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ - } while(0); - -/* Forward declaration of function in hiredis.c */ -int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); - -/* Functions managing dictionary of callbacks for pub/sub. */ -static unsigned int callbackHash(const void *key) { - return dictGenHashFunction((const unsigned char *)key, - sdslen((const sds)key)); -} - -static void *callbackValDup(void *privdata, const void *src) { - ((void) privdata); - redisCallback *dup = malloc(sizeof(*dup)); - memcpy(dup,src,sizeof(*dup)); - return dup; -} - -static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { - int l1, l2; - ((void) privdata); - - l1 = sdslen((const sds)key1); - l2 = sdslen((const sds)key2); - if (l1 != l2) return 0; - return memcmp(key1,key2,l1) == 0; -} - -static void callbackKeyDestructor(void *privdata, void *key) { - ((void) privdata); - sdsfree((sds)key); -} - -static void callbackValDestructor(void *privdata, void *val) { - ((void) privdata); - free(val); -} - -static dictType callbackDict = { - callbackHash, - NULL, - callbackValDup, - callbackKeyCompare, - callbackKeyDestructor, - callbackValDestructor -}; - -static redisAsyncContext *redisAsyncInitialize(redisContext *c) { - redisAsyncContext *ac; - - ac = realloc(c,sizeof(redisAsyncContext)); - if (ac == NULL) - return NULL; - - c = &(ac->c); - - /* The regular connect functions will always set the flag REDIS_CONNECTED. - * For the async API, we want to wait until the first write event is - * received up before setting this flag, so reset it here. */ - c->flags &= ~REDIS_CONNECTED; - - ac->err = 0; - ac->errstr = NULL; - ac->data = NULL; - ac->dataHandler = NULL; - - ac->ev.data = NULL; - ac->ev.addRead = NULL; - ac->ev.delRead = NULL; - ac->ev.addWrite = NULL; - ac->ev.delWrite = NULL; - ac->ev.cleanup = NULL; - - ac->onConnect = NULL; - ac->onDisconnect = NULL; - - ac->replies.head = NULL; - ac->replies.tail = NULL; - ac->sub.invalid.head = NULL; - ac->sub.invalid.tail = NULL; - ac->sub.channels = dictCreate(&callbackDict,NULL); - ac->sub.patterns = dictCreate(&callbackDict,NULL); - return ac; -} - -/* We want the error field to be accessible directly instead of requiring - * an indirection to the redisContext struct. */ -static void __redisAsyncCopyError(redisAsyncContext *ac) { - if (!ac) - return; - - redisContext *c = &(ac->c); - ac->err = c->err; - ac->errstr = c->errstr; -} - -redisAsyncContext *redisAsyncConnect(const char *ip, int port) { - redisContext *c; - redisAsyncContext *ac; - - c = redisConnectNonBlock(ip,port); - if (c == NULL) - return NULL; - - ac = redisAsyncInitialize(c); - if (ac == NULL) { - redisFree(c); - return NULL; - } - - __redisAsyncCopyError(ac); - return ac; -} - -redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, - const char *source_addr) { - redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); - redisAsyncContext *ac = redisAsyncInitialize(c); - __redisAsyncCopyError(ac); - return ac; -} - -redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, - const char *source_addr) { - redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); - redisAsyncContext *ac = redisAsyncInitialize(c); - __redisAsyncCopyError(ac); - return ac; -} - -redisAsyncContext *redisAsyncConnectUnix(const char *path) { - redisContext *c; - redisAsyncContext *ac; - - c = redisConnectUnixNonBlock(path); - if (c == NULL) - return NULL; - - ac = redisAsyncInitialize(c); - if (ac == NULL) { - redisFree(c); - return NULL; - } - - __redisAsyncCopyError(ac); - return ac; -} - -int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { - if (ac->onConnect == NULL) { - ac->onConnect = fn; - - /* The common way to detect an established connection is to wait for - * the first write event to be fired. This assumes the related event - * library functions are already set. */ - _EL_ADD_WRITE(ac); - return REDIS_OK; - } - return REDIS_ERR; -} - -int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { - if (ac->onDisconnect == NULL) { - ac->onDisconnect = fn; - return REDIS_OK; - } - return REDIS_ERR; -} - -/* Helper functions to push/shift callbacks */ -static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { - redisCallback *cb; - - /* Copy callback from stack to heap */ - cb = malloc(sizeof(*cb)); - if (cb == NULL) - return REDIS_ERR_OOM; - - if (source != NULL) { - memcpy(cb,source,sizeof(*cb)); - cb->next = NULL; - } - - /* Store callback in list */ - if (list->head == NULL) - list->head = cb; - if (list->tail != NULL) - list->tail->next = cb; - list->tail = cb; - return REDIS_OK; -} - -static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { - redisCallback *cb = list->head; - if (cb != NULL) { - list->head = cb->next; - if (cb == list->tail) - list->tail = NULL; - - /* Copy callback from heap to stack */ - if (target != NULL) - memcpy(target,cb,sizeof(*cb)); - free(cb); - return REDIS_OK; - } - return REDIS_ERR; -} - -static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { - redisContext *c = &(ac->c); - if (cb->fn != NULL) { - c->flags |= REDIS_IN_CALLBACK; - cb->fn(ac,reply,cb->privdata); - c->flags &= ~REDIS_IN_CALLBACK; - } -} - -/* Helper function to free the context. */ -static void __redisAsyncFree(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisCallback cb; - dictIterator *it; - dictEntry *de; - - /* Execute pending callbacks with NULL reply. */ - while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) - __redisRunCallback(ac,&cb,NULL); - - /* Execute callbacks for invalid commands */ - while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) - __redisRunCallback(ac,&cb,NULL); - - /* Run subscription callbacks callbacks with NULL reply */ - it = dictGetIterator(ac->sub.channels); - while ((de = dictNext(it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - dictReleaseIterator(it); - dictRelease(ac->sub.channels); - - it = dictGetIterator(ac->sub.patterns); - while ((de = dictNext(it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - dictReleaseIterator(it); - dictRelease(ac->sub.patterns); - - /* Signal event lib to clean up */ - _EL_CLEANUP(ac); - - /* Execute disconnect callback. When redisAsyncFree() initiated destroying - * this context, the status will always be REDIS_OK. */ - if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { - if (c->flags & REDIS_FREEING) { - ac->onDisconnect(ac,REDIS_OK); - } else { - ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); - } - } - - if (ac->dataHandler) { - ac->dataHandler(ac); - } - - /* Cleanup self */ - redisFree(c); -} - -/* Free the async context. When this function is called from a callback, - * control needs to be returned to redisProcessCallbacks() before actual - * free'ing. To do so, a flag is set on the context which is picked up by - * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ -void redisAsyncFree(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - c->flags |= REDIS_FREEING; - if (!(c->flags & REDIS_IN_CALLBACK)) - __redisAsyncFree(ac); -} - -/* Helper function to make the disconnect happen and clean up. */ -static void __redisAsyncDisconnect(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - /* Make sure error is accessible if there is any */ - __redisAsyncCopyError(ac); - - if (ac->err == 0) { - /* For clean disconnects, there should be no pending callbacks. */ - assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR); - } else { - /* Disconnection is caused by an error, make sure that pending - * callbacks cannot call new commands. */ - c->flags |= REDIS_DISCONNECTING; - } - - /* For non-clean disconnects, __redisAsyncFree() will execute pending - * callbacks with a NULL-reply. */ - __redisAsyncFree(ac); -} - -/* Tries to do a clean disconnect from Redis, meaning it stops new commands - * from being issued, but tries to flush the output buffer and execute - * callbacks for all remaining replies. When this function is called from a - * callback, there might be more replies and we can safely defer disconnecting - * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately - * when there are no pending callbacks. */ -void redisAsyncDisconnect(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - c->flags |= REDIS_DISCONNECTING; - if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) - __redisAsyncDisconnect(ac); -} - -static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { - redisContext *c = &(ac->c); - dict *callbacks; - dictEntry *de; - int pvariant; - char *stype; - sds sname; - - /* Custom reply functions are not supported for pub/sub. This will fail - * very hard when they are used... */ - if (reply->type == REDIS_REPLY_ARRAY) { - assert(reply->elements >= 2); - assert(reply->element[0]->type == REDIS_REPLY_STRING); - stype = reply->element[0]->str; - pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; - - if (pvariant) - callbacks = ac->sub.patterns; - else - callbacks = ac->sub.channels; - - /* Locate the right callback */ - assert(reply->element[1]->type == REDIS_REPLY_STRING); - sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); - de = dictFind(callbacks,sname); - if (de != NULL) { - memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb)); - - /* If this is an unsubscribe message, remove it. */ - if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { - dictDelete(callbacks,sname); - - /* If this was the last unsubscribe message, revert to - * non-subscribe mode. */ - assert(reply->element[2]->type == REDIS_REPLY_INTEGER); - if (reply->element[2]->integer == 0) - c->flags &= ~REDIS_SUBSCRIBED; - } - } - sdsfree(sname); - } else { - /* Shift callback for invalid commands. */ - __redisShiftCallback(&ac->sub.invalid,dstcb); - } - return REDIS_OK; -} - -void redisProcessCallbacks(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisCallback cb = {NULL, NULL, NULL}; - void *reply = NULL; - int status; - - while((status = redisGetReply(c,&reply)) == REDIS_OK) { - if (reply == NULL) { - /* When the connection is being disconnected and there are - * no more replies, this is the cue to really disconnect. */ - if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) { - __redisAsyncDisconnect(ac); - return; - } - - /* If monitor mode, repush callback */ - if(c->flags & REDIS_MONITORING) { - __redisPushCallback(&ac->replies,&cb); - } - - /* When the connection is not being disconnected, simply stop - * trying to get replies and wait for the next loop tick. */ - break; - } - - /* Even if the context is subscribed, pending regular callbacks will - * get a reply before pub/sub messages arrive. */ - if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { - /* - * A spontaneous reply in a not-subscribed context can be the error - * reply that is sent when a new connection exceeds the maximum - * number of allowed connections on the server side. - * - * This is seen as an error instead of a regular reply because the - * server closes the connection after sending it. - * - * To prevent the error from being overwritten by an EOF error the - * connection is closed here. See issue #43. - * - * Another possibility is that the server is loading its dataset. - * In this case we also want to close the connection, and have the - * user wait until the server is ready to take our request. - */ - if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { - c->err = REDIS_ERR_OTHER; - snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); - c->reader->fn->freeObject(reply); - __redisAsyncDisconnect(ac); - return; - } - /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ - assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); - if(c->flags & REDIS_SUBSCRIBED) - __redisGetSubscribeCallback(ac,reply,&cb); - } - - if (cb.fn != NULL) { - __redisRunCallback(ac,&cb,reply); - c->reader->fn->freeObject(reply); - - /* Proceed with free'ing when redisAsyncFree() was called. */ - if (c->flags & REDIS_FREEING) { - __redisAsyncFree(ac); - return; - } - } else { - /* No callback for this reply. This can either be a NULL callback, - * or there were no callbacks to begin with. Either way, don't - * abort with an error, but simply ignore it because the client - * doesn't know what the server will spit out over the wire. */ - c->reader->fn->freeObject(reply); - } - } - - /* Disconnect when there was an error reading the reply */ - if (status != REDIS_OK) - __redisAsyncDisconnect(ac); -} - -/* Internal helper function to detect socket status the first time a read or - * write event fires. When connecting was not succesful, the connect callback - * is called with a REDIS_ERR status and the context is free'd. */ -static int __redisAsyncHandleConnect(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - if (redisCheckSocketError(c) == REDIS_ERR) { - /* Try again later when connect(2) is still in progress. */ - if (errno == EINPROGRESS) - return REDIS_OK; - - if (ac->onConnect) ac->onConnect(ac,REDIS_ERR); - __redisAsyncDisconnect(ac); - return REDIS_ERR; - } - - /* Mark context as connected. */ - c->flags |= REDIS_CONNECTED; - if (ac->onConnect) ac->onConnect(ac,REDIS_OK); - return REDIS_OK; -} - -/* This function should be called when the socket is readable. - * It processes all replies that can be read and executes their callbacks. - */ -void redisAsyncHandleRead(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - if (!(c->flags & REDIS_CONNECTED)) { - /* Abort connect was not successful. */ - if (__redisAsyncHandleConnect(ac) != REDIS_OK) - return; - /* Try again later when the context is still not connected. */ - if (!(c->flags & REDIS_CONNECTED)) - return; - } - - if (redisBufferRead(c) == REDIS_ERR) { - __redisAsyncDisconnect(ac); - } else { - /* Always re-schedule reads */ - _EL_ADD_READ(ac); - redisProcessCallbacks(ac); - } -} - -void redisAsyncHandleWrite(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - int done = 0; - - if (!(c->flags & REDIS_CONNECTED)) { - /* Abort connect was not successful. */ - if (__redisAsyncHandleConnect(ac) != REDIS_OK) - return; - /* Try again later when the context is still not connected. */ - if (!(c->flags & REDIS_CONNECTED)) - return; - } - - if (redisBufferWrite(c,&done) == REDIS_ERR) { - __redisAsyncDisconnect(ac); - } else { - /* Continue writing when not done, stop writing otherwise */ - if (!done) - _EL_ADD_WRITE(ac); - else - _EL_DEL_WRITE(ac); - - /* Always schedule reads after writes */ - _EL_ADD_READ(ac); - } -} - -/* Sets a pointer to the first argument and its length starting at p. Returns - * the number of bytes to skip to get to the following argument. */ -static const char *nextArgument(const char *start, const char **str, size_t *len) { - const char *p = start; - if (p[0] != '$') { - p = strchr(p,'$'); - if (p == NULL) return NULL; - } - - *len = (int)strtol(p+1,NULL,10); - p = strchr(p,'\r'); - assert(p); - *str = p+2; - return p+2+(*len)+2; -} - -/* Helper function for the redisAsyncCommand* family of functions. Writes a - * formatted command to the output buffer and registers the provided callback - * function with the context. */ -static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { - redisContext *c = &(ac->c); - redisCallback cb; - int pvariant, hasnext; - const char *cstr, *astr; - size_t clen, alen; - const char *p; - sds sname; - int ret; - - /* Don't accept new commands when the connection is about to be closed. */ - if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; - - /* Setup callback */ - cb.fn = fn; - cb.privdata = privdata; - - /* Find out which command will be appended. */ - p = nextArgument(cmd,&cstr,&clen); - assert(p != NULL); - hasnext = (p[0] == '$'); - pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; - cstr += pvariant; - clen -= pvariant; - - if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { - c->flags |= REDIS_SUBSCRIBED; - - /* Add every channel/pattern to the list of subscription callbacks. */ - while ((p = nextArgument(p,&astr,&alen)) != NULL) { - sname = sdsnewlen(astr,alen); - if (pvariant) - ret = dictReplace(ac->sub.patterns,sname,&cb); - else - ret = dictReplace(ac->sub.channels,sname,&cb); - - if (ret == 0) sdsfree(sname); - } - } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { - /* It is only useful to call (P)UNSUBSCRIBE when the context is - * subscribed to one or more channels or patterns. */ - if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; - - /* (P)UNSUBSCRIBE does not have its own response: every channel or - * pattern that is unsubscribed will receive a message. This means we - * should not append a callback function for this command. */ - } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) { - /* Set monitor flag and push callback */ - c->flags |= REDIS_MONITORING; - __redisPushCallback(&ac->replies,&cb); - } else { - if (c->flags & REDIS_SUBSCRIBED) - /* This will likely result in an error reply, but it needs to be - * received and passed to the callback. */ - __redisPushCallback(&ac->sub.invalid,&cb); - else - __redisPushCallback(&ac->replies,&cb); - } - - __redisAppendCommand(c,cmd,len); - - /* Always schedule a write when the write buffer is non-empty */ - _EL_ADD_WRITE(ac); - - return REDIS_OK; -} - -int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { - char *cmd; - int len; - int status; - len = redisvFormatCommand(&cmd,format,ap); - - /* We don't want to pass -1 or -2 to future functions as a length. */ - if (len < 0) - return REDIS_ERR; - - status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - free(cmd); - return status; -} - -int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { - va_list ap; - int status; - va_start(ap,format); - status = redisvAsyncCommand(ac,fn,privdata,format,ap); - va_end(ap); - return status; -} - -int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { - sds cmd; - int len; - int status; - len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); - status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - sdsfree(cmd); - return status; -} - -int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { - int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - return status; -} diff --git a/ext/hiredis-vip-0.3.0/command.c b/ext/hiredis-vip-0.3.0/command.c deleted file mode 100644 index e32091b40..000000000 --- a/ext/hiredis-vip-0.3.0/command.c +++ /dev/null @@ -1,1700 +0,0 @@ -#include -#include - -#include "command.h" -#include "hiutil.h" -#include "hiarray.h" - - -static uint64_t cmd_id = 0; /* command id counter */ - - -/* - * Return true, if the redis command take no key, otherwise - * return false - */ -static int -redis_argz(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_PING: - case CMD_REQ_REDIS_QUIT: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command accepts no arguments, otherwise - * return false - */ -static int -redis_arg0(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_EXISTS: - case CMD_REQ_REDIS_PERSIST: - case CMD_REQ_REDIS_PTTL: - case CMD_REQ_REDIS_SORT: - case CMD_REQ_REDIS_TTL: - case CMD_REQ_REDIS_TYPE: - case CMD_REQ_REDIS_DUMP: - - case CMD_REQ_REDIS_DECR: - case CMD_REQ_REDIS_GET: - case CMD_REQ_REDIS_INCR: - case CMD_REQ_REDIS_STRLEN: - - case CMD_REQ_REDIS_HGETALL: - case CMD_REQ_REDIS_HKEYS: - case CMD_REQ_REDIS_HLEN: - case CMD_REQ_REDIS_HVALS: - - case CMD_REQ_REDIS_LLEN: - case CMD_REQ_REDIS_LPOP: - case CMD_REQ_REDIS_RPOP: - - case CMD_REQ_REDIS_SCARD: - case CMD_REQ_REDIS_SMEMBERS: - case CMD_REQ_REDIS_SPOP: - - case CMD_REQ_REDIS_ZCARD: - case CMD_REQ_REDIS_PFCOUNT: - case CMD_REQ_REDIS_AUTH: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command accepts exactly 1 argument, otherwise - * return false - */ -static int -redis_arg1(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_EXPIRE: - case CMD_REQ_REDIS_EXPIREAT: - case CMD_REQ_REDIS_PEXPIRE: - case CMD_REQ_REDIS_PEXPIREAT: - - case CMD_REQ_REDIS_APPEND: - case CMD_REQ_REDIS_DECRBY: - case CMD_REQ_REDIS_GETBIT: - case CMD_REQ_REDIS_GETSET: - case CMD_REQ_REDIS_INCRBY: - case CMD_REQ_REDIS_INCRBYFLOAT: - case CMD_REQ_REDIS_SETNX: - - case CMD_REQ_REDIS_HEXISTS: - case CMD_REQ_REDIS_HGET: - - case CMD_REQ_REDIS_LINDEX: - case CMD_REQ_REDIS_LPUSHX: - case CMD_REQ_REDIS_RPOPLPUSH: - case CMD_REQ_REDIS_RPUSHX: - - case CMD_REQ_REDIS_SISMEMBER: - - case CMD_REQ_REDIS_ZRANK: - case CMD_REQ_REDIS_ZREVRANK: - case CMD_REQ_REDIS_ZSCORE: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command accepts exactly 2 arguments, otherwise - * return false - */ -static int -redis_arg2(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_GETRANGE: - case CMD_REQ_REDIS_PSETEX: - case CMD_REQ_REDIS_SETBIT: - case CMD_REQ_REDIS_SETEX: - case CMD_REQ_REDIS_SETRANGE: - - case CMD_REQ_REDIS_HINCRBY: - case CMD_REQ_REDIS_HINCRBYFLOAT: - case CMD_REQ_REDIS_HSET: - case CMD_REQ_REDIS_HSETNX: - - case CMD_REQ_REDIS_LRANGE: - case CMD_REQ_REDIS_LREM: - case CMD_REQ_REDIS_LSET: - case CMD_REQ_REDIS_LTRIM: - - case CMD_REQ_REDIS_SMOVE: - - case CMD_REQ_REDIS_ZCOUNT: - case CMD_REQ_REDIS_ZLEXCOUNT: - case CMD_REQ_REDIS_ZINCRBY: - case CMD_REQ_REDIS_ZREMRANGEBYLEX: - case CMD_REQ_REDIS_ZREMRANGEBYRANK: - case CMD_REQ_REDIS_ZREMRANGEBYSCORE: - - case CMD_REQ_REDIS_RESTORE: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command accepts exactly 3 arguments, otherwise - * return false - */ -static int -redis_arg3(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_LINSERT: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command accepts 0 or more arguments, otherwise - * return false - */ -static int -redis_argn(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_BITCOUNT: - - case CMD_REQ_REDIS_SET: - case CMD_REQ_REDIS_HDEL: - case CMD_REQ_REDIS_HMGET: - case CMD_REQ_REDIS_HMSET: - case CMD_REQ_REDIS_HSCAN: - - case CMD_REQ_REDIS_LPUSH: - case CMD_REQ_REDIS_RPUSH: - - case CMD_REQ_REDIS_SADD: - case CMD_REQ_REDIS_SDIFF: - case CMD_REQ_REDIS_SDIFFSTORE: - case CMD_REQ_REDIS_SINTER: - case CMD_REQ_REDIS_SINTERSTORE: - case CMD_REQ_REDIS_SREM: - case CMD_REQ_REDIS_SUNION: - case CMD_REQ_REDIS_SUNIONSTORE: - case CMD_REQ_REDIS_SRANDMEMBER: - case CMD_REQ_REDIS_SSCAN: - - case CMD_REQ_REDIS_PFADD: - case CMD_REQ_REDIS_PFMERGE: - - case CMD_REQ_REDIS_ZADD: - case CMD_REQ_REDIS_ZINTERSTORE: - case CMD_REQ_REDIS_ZRANGE: - case CMD_REQ_REDIS_ZRANGEBYSCORE: - case CMD_REQ_REDIS_ZREM: - case CMD_REQ_REDIS_ZREVRANGE: - case CMD_REQ_REDIS_ZRANGEBYLEX: - case CMD_REQ_REDIS_ZREVRANGEBYSCORE: - case CMD_REQ_REDIS_ZUNIONSTORE: - case CMD_REQ_REDIS_ZSCAN: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command is a vector command accepting one or - * more keys, otherwise return false - */ -static int -redis_argx(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_MGET: - case CMD_REQ_REDIS_DEL: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command is a vector command accepting one or - * more key-value pairs, otherwise return false - */ -static int -redis_argkvx(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_MSET: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command is either EVAL or EVALSHA. These commands - * have a special format with exactly 2 arguments, followed by one or more keys, - * followed by zero or more arguments (the documentation online seems to suggest - * that at least one argument is required, but that shouldn't be the case). - */ -static int -redis_argeval(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_EVAL: - case CMD_REQ_REDIS_EVALSHA: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Reference: http://redis.io/topics/protocol - * - * Redis >= 1.2 uses the unified protocol to send requests to the Redis - * server. In the unified protocol all the arguments sent to the server - * are binary safe and every request has the following general form: - * - * * CR LF - * $ CR LF - * CR LF - * ... - * $ CR LF - * CR LF - * - * Before the unified request protocol, redis protocol for requests supported - * the following commands - * 1). Inline commands: simple commands where arguments are just space - * separated strings. No binary safeness is possible. - * 2). Bulk commands: bulk commands are exactly like inline commands, but - * the last argument is handled in a special way in order to allow for - * a binary-safe last argument. - * - * only supports the Redis unified protocol for requests. - */ -void -redis_parse_cmd(struct cmd *r) -{ - int len; - char *p, *m, *token = NULL; - char *cmd_end; - char ch; - uint32_t rlen = 0; /* running length in parsing fsa */ - uint32_t rnarg = 0; /* running # arg used by parsing fsa */ - enum { - SW_START, - SW_NARG, - SW_NARG_LF, - SW_REQ_TYPE_LEN, - SW_REQ_TYPE_LEN_LF, - SW_REQ_TYPE, - SW_REQ_TYPE_LF, - SW_KEY_LEN, - SW_KEY_LEN_LF, - SW_KEY, - SW_KEY_LF, - SW_ARG1_LEN, - SW_ARG1_LEN_LF, - SW_ARG1, - SW_ARG1_LF, - SW_ARG2_LEN, - SW_ARG2_LEN_LF, - SW_ARG2, - SW_ARG2_LF, - SW_ARG3_LEN, - SW_ARG3_LEN_LF, - SW_ARG3, - SW_ARG3_LF, - SW_ARGN_LEN, - SW_ARGN_LEN_LF, - SW_ARGN, - SW_ARGN_LF, - SW_SENTINEL - } state; - - state = SW_START; - cmd_end = r->cmd + r->clen; - - ASSERT(state >= SW_START && state < SW_SENTINEL); - ASSERT(r->cmd != NULL && r->clen > 0); - - for (p = r->cmd; p < cmd_end; p++) { - ch = *p; - - switch (state) { - - case SW_START: - case SW_NARG: - if (token == NULL) { - if (ch != '*') { - goto error; - } - token = p; - /* req_start <- p */ - r->narg_start = p; - rnarg = 0; - state = SW_NARG; - } else if (isdigit(ch)) { - rnarg = rnarg * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - if (rnarg == 0) { - goto error; - } - r->narg = rnarg; - r->narg_end = p; - token = NULL; - state = SW_NARG_LF; - } else { - goto error; - } - - break; - - case SW_NARG_LF: - switch (ch) { - case LF: - state = SW_REQ_TYPE_LEN; - break; - - default: - goto error; - } - - break; - - case SW_REQ_TYPE_LEN: - if (token == NULL) { - if (ch != '$') { - goto error; - } - token = p; - rlen = 0; - } else if (isdigit(ch)) { - rlen = rlen * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - if (rlen == 0 || rnarg == 0) { - goto error; - } - rnarg--; - token = NULL; - state = SW_REQ_TYPE_LEN_LF; - } else { - goto error; - } - - break; - - case SW_REQ_TYPE_LEN_LF: - switch (ch) { - case LF: - state = SW_REQ_TYPE; - break; - - default: - goto error; - } - - break; - - case SW_REQ_TYPE: - if (token == NULL) { - token = p; - } - - m = token + rlen; - if (m >= cmd_end) { - //m = cmd_end - 1; - //p = m; - //break; - goto error; - } - - if (*m != CR) { - goto error; - } - - p = m; /* move forward by rlen bytes */ - rlen = 0; - m = token; - token = NULL; - r->type = CMD_UNKNOWN; - - switch (p - m) { - - case 3: - if (str3icmp(m, 'g', 'e', 't')) { - r->type = CMD_REQ_REDIS_GET; - break; - } - - if (str3icmp(m, 's', 'e', 't')) { - r->type = CMD_REQ_REDIS_SET; - break; - } - - if (str3icmp(m, 't', 't', 'l')) { - r->type = CMD_REQ_REDIS_TTL; - break; - } - - if (str3icmp(m, 'd', 'e', 'l')) { - r->type = CMD_REQ_REDIS_DEL; - break; - } - - break; - - case 4: - if (str4icmp(m, 'p', 't', 't', 'l')) { - r->type = CMD_REQ_REDIS_PTTL; - break; - } - - if (str4icmp(m, 'd', 'e', 'c', 'r')) { - r->type = CMD_REQ_REDIS_DECR; - break; - } - - if (str4icmp(m, 'd', 'u', 'm', 'p')) { - r->type = CMD_REQ_REDIS_DUMP; - break; - } - - if (str4icmp(m, 'h', 'd', 'e', 'l')) { - r->type = CMD_REQ_REDIS_HDEL; - break; - } - - if (str4icmp(m, 'h', 'g', 'e', 't')) { - r->type = CMD_REQ_REDIS_HGET; - break; - } - - if (str4icmp(m, 'h', 'l', 'e', 'n')) { - r->type = CMD_REQ_REDIS_HLEN; - break; - } - - if (str4icmp(m, 'h', 's', 'e', 't')) { - r->type = CMD_REQ_REDIS_HSET; - break; - } - - if (str4icmp(m, 'i', 'n', 'c', 'r')) { - r->type = CMD_REQ_REDIS_INCR; - break; - } - - if (str4icmp(m, 'l', 'l', 'e', 'n')) { - r->type = CMD_REQ_REDIS_LLEN; - break; - } - - if (str4icmp(m, 'l', 'p', 'o', 'p')) { - r->type = CMD_REQ_REDIS_LPOP; - break; - } - - if (str4icmp(m, 'l', 'r', 'e', 'm')) { - r->type = CMD_REQ_REDIS_LREM; - break; - } - - if (str4icmp(m, 'l', 's', 'e', 't')) { - r->type = CMD_REQ_REDIS_LSET; - break; - } - - if (str4icmp(m, 'r', 'p', 'o', 'p')) { - r->type = CMD_REQ_REDIS_RPOP; - break; - } - - if (str4icmp(m, 's', 'a', 'd', 'd')) { - r->type = CMD_REQ_REDIS_SADD; - break; - } - - if (str4icmp(m, 's', 'p', 'o', 'p')) { - r->type = CMD_REQ_REDIS_SPOP; - break; - } - - if (str4icmp(m, 's', 'r', 'e', 'm')) { - r->type = CMD_REQ_REDIS_SREM; - break; - } - - if (str4icmp(m, 't', 'y', 'p', 'e')) { - r->type = CMD_REQ_REDIS_TYPE; - break; - } - - if (str4icmp(m, 'm', 'g', 'e', 't')) { - r->type = CMD_REQ_REDIS_MGET; - break; - } - if (str4icmp(m, 'm', 's', 'e', 't')) { - r->type = CMD_REQ_REDIS_MSET; - break; - } - - if (str4icmp(m, 'z', 'a', 'd', 'd')) { - r->type = CMD_REQ_REDIS_ZADD; - break; - } - - if (str4icmp(m, 'z', 'r', 'e', 'm')) { - r->type = CMD_REQ_REDIS_ZREM; - break; - } - - if (str4icmp(m, 'e', 'v', 'a', 'l')) { - r->type = CMD_REQ_REDIS_EVAL; - break; - } - - if (str4icmp(m, 's', 'o', 'r', 't')) { - r->type = CMD_REQ_REDIS_SORT; - break; - } - - if (str4icmp(m, 'p', 'i', 'n', 'g')) { - r->type = CMD_REQ_REDIS_PING; - r->noforward = 1; - break; - } - - if (str4icmp(m, 'q', 'u', 'i', 't')) { - r->type = CMD_REQ_REDIS_QUIT; - r->quit = 1; - break; - } - - if (str4icmp(m, 'a', 'u', 't', 'h')) { - r->type = CMD_REQ_REDIS_AUTH; - r->noforward = 1; - break; - } - - break; - - case 5: - if (str5icmp(m, 'h', 'k', 'e', 'y', 's')) { - r->type = CMD_REQ_REDIS_HKEYS; - break; - } - - if (str5icmp(m, 'h', 'm', 'g', 'e', 't')) { - r->type = CMD_REQ_REDIS_HMGET; - break; - } - - if (str5icmp(m, 'h', 'm', 's', 'e', 't')) { - r->type = CMD_REQ_REDIS_HMSET; - break; - } - - if (str5icmp(m, 'h', 'v', 'a', 'l', 's')) { - r->type = CMD_REQ_REDIS_HVALS; - break; - } - - if (str5icmp(m, 'h', 's', 'c', 'a', 'n')) { - r->type = CMD_REQ_REDIS_HSCAN; - break; - } - - if (str5icmp(m, 'l', 'p', 'u', 's', 'h')) { - r->type = CMD_REQ_REDIS_LPUSH; - break; - } - - if (str5icmp(m, 'l', 't', 'r', 'i', 'm')) { - r->type = CMD_REQ_REDIS_LTRIM; - break; - } - - if (str5icmp(m, 'r', 'p', 'u', 's', 'h')) { - r->type = CMD_REQ_REDIS_RPUSH; - break; - } - - if (str5icmp(m, 's', 'c', 'a', 'r', 'd')) { - r->type = CMD_REQ_REDIS_SCARD; - break; - } - - if (str5icmp(m, 's', 'd', 'i', 'f', 'f')) { - r->type = CMD_REQ_REDIS_SDIFF; - break; - } - - if (str5icmp(m, 's', 'e', 't', 'e', 'x')) { - r->type = CMD_REQ_REDIS_SETEX; - break; - } - - if (str5icmp(m, 's', 'e', 't', 'n', 'x')) { - r->type = CMD_REQ_REDIS_SETNX; - break; - } - - if (str5icmp(m, 's', 'm', 'o', 'v', 'e')) { - r->type = CMD_REQ_REDIS_SMOVE; - break; - } - - if (str5icmp(m, 's', 's', 'c', 'a', 'n')) { - r->type = CMD_REQ_REDIS_SSCAN; - break; - } - - if (str5icmp(m, 'z', 'c', 'a', 'r', 'd')) { - r->type = CMD_REQ_REDIS_ZCARD; - break; - } - - if (str5icmp(m, 'z', 'r', 'a', 'n', 'k')) { - r->type = CMD_REQ_REDIS_ZRANK; - break; - } - - if (str5icmp(m, 'z', 's', 'c', 'a', 'n')) { - r->type = CMD_REQ_REDIS_ZSCAN; - break; - } - - if (str5icmp(m, 'p', 'f', 'a', 'd', 'd')) { - r->type = CMD_REQ_REDIS_PFADD; - break; - } - - break; - - case 6: - if (str6icmp(m, 'a', 'p', 'p', 'e', 'n', 'd')) { - r->type = CMD_REQ_REDIS_APPEND; - break; - } - - if (str6icmp(m, 'd', 'e', 'c', 'r', 'b', 'y')) { - r->type = CMD_REQ_REDIS_DECRBY; - break; - } - - if (str6icmp(m, 'e', 'x', 'i', 's', 't', 's')) { - r->type = CMD_REQ_REDIS_EXISTS; - break; - } - - if (str6icmp(m, 'e', 'x', 'p', 'i', 'r', 'e')) { - r->type = CMD_REQ_REDIS_EXPIRE; - break; - } - - if (str6icmp(m, 'g', 'e', 't', 'b', 'i', 't')) { - r->type = CMD_REQ_REDIS_GETBIT; - break; - } - - if (str6icmp(m, 'g', 'e', 't', 's', 'e', 't')) { - r->type = CMD_REQ_REDIS_GETSET; - break; - } - - if (str6icmp(m, 'p', 's', 'e', 't', 'e', 'x')) { - r->type = CMD_REQ_REDIS_PSETEX; - break; - } - - if (str6icmp(m, 'h', 's', 'e', 't', 'n', 'x')) { - r->type = CMD_REQ_REDIS_HSETNX; - break; - } - - if (str6icmp(m, 'i', 'n', 'c', 'r', 'b', 'y')) { - r->type = CMD_REQ_REDIS_INCRBY; - break; - } - - if (str6icmp(m, 'l', 'i', 'n', 'd', 'e', 'x')) { - r->type = CMD_REQ_REDIS_LINDEX; - break; - } - - if (str6icmp(m, 'l', 'p', 'u', 's', 'h', 'x')) { - r->type = CMD_REQ_REDIS_LPUSHX; - break; - } - - if (str6icmp(m, 'l', 'r', 'a', 'n', 'g', 'e')) { - r->type = CMD_REQ_REDIS_LRANGE; - break; - } - - if (str6icmp(m, 'r', 'p', 'u', 's', 'h', 'x')) { - r->type = CMD_REQ_REDIS_RPUSHX; - break; - } - - if (str6icmp(m, 's', 'e', 't', 'b', 'i', 't')) { - r->type = CMD_REQ_REDIS_SETBIT; - break; - } - - if (str6icmp(m, 's', 'i', 'n', 't', 'e', 'r')) { - r->type = CMD_REQ_REDIS_SINTER; - break; - } - - if (str6icmp(m, 's', 't', 'r', 'l', 'e', 'n')) { - r->type = CMD_REQ_REDIS_STRLEN; - break; - } - - if (str6icmp(m, 's', 'u', 'n', 'i', 'o', 'n')) { - r->type = CMD_REQ_REDIS_SUNION; - break; - } - - if (str6icmp(m, 'z', 'c', 'o', 'u', 'n', 't')) { - r->type = CMD_REQ_REDIS_ZCOUNT; - break; - } - - if (str6icmp(m, 'z', 'r', 'a', 'n', 'g', 'e')) { - r->type = CMD_REQ_REDIS_ZRANGE; - break; - } - - if (str6icmp(m, 'z', 's', 'c', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_ZSCORE; - break; - } - - break; - - case 7: - if (str7icmp(m, 'p', 'e', 'r', 's', 'i', 's', 't')) { - r->type = CMD_REQ_REDIS_PERSIST; - break; - } - - if (str7icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e')) { - r->type = CMD_REQ_REDIS_PEXPIRE; - break; - } - - if (str7icmp(m, 'h', 'e', 'x', 'i', 's', 't', 's')) { - r->type = CMD_REQ_REDIS_HEXISTS; - break; - } - - if (str7icmp(m, 'h', 'g', 'e', 't', 'a', 'l', 'l')) { - r->type = CMD_REQ_REDIS_HGETALL; - break; - } - - if (str7icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y')) { - r->type = CMD_REQ_REDIS_HINCRBY; - break; - } - - if (str7icmp(m, 'l', 'i', 'n', 's', 'e', 'r', 't')) { - r->type = CMD_REQ_REDIS_LINSERT; - break; - } - - if (str7icmp(m, 'z', 'i', 'n', 'c', 'r', 'b', 'y')) { - r->type = CMD_REQ_REDIS_ZINCRBY; - break; - } - - if (str7icmp(m, 'e', 'v', 'a', 'l', 's', 'h', 'a')) { - r->type = CMD_REQ_REDIS_EVALSHA; - break; - } - - if (str7icmp(m, 'r', 'e', 's', 't', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_RESTORE; - break; - } - - if (str7icmp(m, 'p', 'f', 'c', 'o', 'u', 'n', 't')) { - r->type = CMD_REQ_REDIS_PFCOUNT; - break; - } - - if (str7icmp(m, 'p', 'f', 'm', 'e', 'r', 'g', 'e')) { - r->type = CMD_REQ_REDIS_PFMERGE; - break; - } - - break; - - case 8: - if (str8icmp(m, 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) { - r->type = CMD_REQ_REDIS_EXPIREAT; - break; - } - - if (str8icmp(m, 'b', 'i', 't', 'c', 'o', 'u', 'n', 't')) { - r->type = CMD_REQ_REDIS_BITCOUNT; - break; - } - - if (str8icmp(m, 'g', 'e', 't', 'r', 'a', 'n', 'g', 'e')) { - r->type = CMD_REQ_REDIS_GETRANGE; - break; - } - - if (str8icmp(m, 's', 'e', 't', 'r', 'a', 'n', 'g', 'e')) { - r->type = CMD_REQ_REDIS_SETRANGE; - break; - } - - if (str8icmp(m, 's', 'm', 'e', 'm', 'b', 'e', 'r', 's')) { - r->type = CMD_REQ_REDIS_SMEMBERS; - break; - } - - if (str8icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'k')) { - r->type = CMD_REQ_REDIS_ZREVRANK; - break; - } - - break; - - case 9: - if (str9icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) { - r->type = CMD_REQ_REDIS_PEXPIREAT; - break; - } - - if (str9icmp(m, 'r', 'p', 'o', 'p', 'l', 'p', 'u', 's', 'h')) { - r->type = CMD_REQ_REDIS_RPOPLPUSH; - break; - } - - if (str9icmp(m, 's', 'i', 's', 'm', 'e', 'm', 'b', 'e', 'r')) { - r->type = CMD_REQ_REDIS_SISMEMBER; - break; - } - - if (str9icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e')) { - r->type = CMD_REQ_REDIS_ZREVRANGE; - break; - } - - if (str9icmp(m, 'z', 'l', 'e', 'x', 'c', 'o', 'u', 'n', 't')) { - r->type = CMD_REQ_REDIS_ZLEXCOUNT; - break; - } - - break; - - case 10: - if (str10icmp(m, 's', 'd', 'i', 'f', 'f', 's', 't', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_SDIFFSTORE; - break; - } - - case 11: - if (str11icmp(m, 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { - r->type = CMD_REQ_REDIS_INCRBYFLOAT; - break; - } - - if (str11icmp(m, 's', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_SINTERSTORE; - break; - } - - if (str11icmp(m, 's', 'r', 'a', 'n', 'd', 'm', 'e', 'm', 'b', 'e', 'r')) { - r->type = CMD_REQ_REDIS_SRANDMEMBER; - break; - } - - if (str11icmp(m, 's', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_SUNIONSTORE; - break; - } - - if (str11icmp(m, 'z', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_ZINTERSTORE; - break; - } - - if (str11icmp(m, 'z', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_ZUNIONSTORE; - break; - } - - if (str11icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) { - r->type = CMD_REQ_REDIS_ZRANGEBYLEX; - break; - } - - break; - - case 12: - if (str12icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { - r->type = CMD_REQ_REDIS_HINCRBYFLOAT; - break; - } - - - break; - - case 13: - if (str13icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_ZRANGEBYSCORE; - break; - } - - break; - - case 14: - if (str14icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) { - r->type = CMD_REQ_REDIS_ZREMRANGEBYLEX; - break; - } - - break; - - case 15: - if (str15icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'r', 'a', 'n', 'k')) { - r->type = CMD_REQ_REDIS_ZREMRANGEBYRANK; - break; - } - - break; - - case 16: - if (str16icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_ZREMRANGEBYSCORE; - break; - } - - if (str16icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_ZREVRANGEBYSCORE; - break; - } - - break; - - default: - break; - } - - if (r->type == CMD_UNKNOWN) { - goto error; - } - - state = SW_REQ_TYPE_LF; - break; - - case SW_REQ_TYPE_LF: - switch (ch) { - case LF: - if (redis_argz(r)) { - goto done; - } else if (redis_argeval(r)) { - state = SW_ARG1_LEN; - } else { - state = SW_KEY_LEN; - } - break; - - default: - goto error; - } - - break; - - case SW_KEY_LEN: - if (token == NULL) { - if (ch != '$') { - goto error; - } - token = p; - rlen = 0; - } else if (isdigit(ch)) { - rlen = rlen * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - - if (rnarg == 0) { - goto error; - } - rnarg--; - token = NULL; - state = SW_KEY_LEN_LF; - } else { - goto error; - } - - break; - - case SW_KEY_LEN_LF: - switch (ch) { - case LF: - state = SW_KEY; - break; - - default: - goto error; - } - - break; - - case SW_KEY: - if (token == NULL) { - token = p; - } - - m = token + rlen; - if (m >= cmd_end) { - //m = b->last - 1; - //p = m; - //break; - goto error; - } - - if (*m != CR) { - goto error; - } else { /* got a key */ - struct keypos *kpos; - - p = m; /* move forward by rlen bytes */ - rlen = 0; - m = token; - token = NULL; - - kpos = hiarray_push(r->keys); - if (kpos == NULL) { - goto enomem; - } - kpos->start = m; - kpos->end = p; - //kpos->v_len = 0; - - state = SW_KEY_LF; - } - - break; - - case SW_KEY_LF: - switch (ch) { - case LF: - if (redis_arg0(r)) { - if (rnarg != 0) { - goto error; - } - goto done; - } else if (redis_arg1(r)) { - if (rnarg != 1) { - goto error; - } - state = SW_ARG1_LEN; - } else if (redis_arg2(r)) { - if (rnarg != 2) { - goto error; - } - state = SW_ARG1_LEN; - } else if (redis_arg3(r)) { - if (rnarg != 3) { - goto error; - } - state = SW_ARG1_LEN; - } else if (redis_argn(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_ARG1_LEN; - } else if (redis_argx(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_KEY_LEN; - } else if (redis_argkvx(r)) { - if (rnarg == 0) { - goto done; - } - if (r->narg % 2 == 0) { - goto error; - } - state = SW_ARG1_LEN; - } else if (redis_argeval(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_ARGN_LEN; - } else { - goto error; - } - - break; - - default: - goto error; - } - - break; - - case SW_ARG1_LEN: - if (token == NULL) { - if (ch != '$') { - goto error; - } - rlen = 0; - token = p; - } else if (isdigit(ch)) { - rlen = rlen * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - if ((p - token) <= 1 || rnarg == 0) { - goto error; - } - rnarg--; - token = NULL; - - /* - //for mset value length - if(redis_argkvx(r)) - { - struct keypos *kpos; - uint32_t array_len = array_n(r->keys); - if(array_len == 0) - { - goto error; - } - - kpos = array_n(r->keys, array_len-1); - if (kpos == NULL || kpos->v_len != 0) { - goto error; - } - - kpos->v_len = rlen; - } - */ - state = SW_ARG1_LEN_LF; - } else { - goto error; - } - - break; - - case SW_ARG1_LEN_LF: - switch (ch) { - case LF: - state = SW_ARG1; - break; - - default: - goto error; - } - - break; - - case SW_ARG1: - m = p + rlen; - if (m >= cmd_end) { - //rlen -= (uint32_t)(b->last - p); - //m = b->last - 1; - //p = m; - //break; - goto error; - } - - if (*m != CR) { - goto error; - } - - p = m; /* move forward by rlen bytes */ - rlen = 0; - - state = SW_ARG1_LF; - - break; - - case SW_ARG1_LF: - switch (ch) { - case LF: - if (redis_arg1(r)) { - if (rnarg != 0) { - goto error; - } - goto done; - } else if (redis_arg2(r)) { - if (rnarg != 1) { - goto error; - } - state = SW_ARG2_LEN; - } else if (redis_arg3(r)) { - if (rnarg != 2) { - goto error; - } - state = SW_ARG2_LEN; - } else if (redis_argn(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_ARGN_LEN; - } else if (redis_argeval(r)) { - if (rnarg < 2) { - goto error; - } - state = SW_ARG2_LEN; - } else if (redis_argkvx(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_KEY_LEN; - } else { - goto error; - } - - break; - - default: - goto error; - } - - break; - - case SW_ARG2_LEN: - if (token == NULL) { - if (ch != '$') { - goto error; - } - rlen = 0; - token = p; - } else if (isdigit(ch)) { - rlen = rlen * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - if ((p - token) <= 1 || rnarg == 0) { - goto error; - } - rnarg--; - token = NULL; - state = SW_ARG2_LEN_LF; - } else { - goto error; - } - - break; - - case SW_ARG2_LEN_LF: - switch (ch) { - case LF: - state = SW_ARG2; - break; - - default: - goto error; - } - - break; - - case SW_ARG2: - if (token == NULL && redis_argeval(r)) { - /* - * For EVAL/EVALSHA, ARG2 represents the # key/arg pairs which must - * be tokenized and stored in contiguous memory. - */ - token = p; - } - - m = p + rlen; - if (m >= cmd_end) { - //rlen -= (uint32_t)(b->last - p); - //m = b->last - 1; - //p = m; - //break; - goto error; - } - - if (*m != CR) { - goto error; - } - - p = m; /* move forward by rlen bytes */ - rlen = 0; - - if (redis_argeval(r)) { - uint32_t nkey; - char *chp; - - /* - * For EVAL/EVALSHA, we need to find the integer value of this - * argument. It tells us the number of keys in the script, and - * we need to error out if number of keys is 0. At this point, - * both p and m point to the end of the argument and r->token - * points to the start. - */ - if (p - token < 1) { - goto error; - } - - for (nkey = 0, chp = token; chp < p; chp++) { - if (isdigit(*chp)) { - nkey = nkey * 10 + (uint32_t)(*chp - '0'); - } else { - goto error; - } - } - if (nkey == 0) { - goto error; - } - - token = NULL; - } - - state = SW_ARG2_LF; - - break; - - case SW_ARG2_LF: - switch (ch) { - case LF: - if (redis_arg2(r)) { - if (rnarg != 0) { - goto error; - } - goto done; - } else if (redis_arg3(r)) { - if (rnarg != 1) { - goto error; - } - state = SW_ARG3_LEN; - } else if (redis_argn(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_ARGN_LEN; - } else if (redis_argeval(r)) { - if (rnarg < 1) { - goto error; - } - state = SW_KEY_LEN; - } else { - goto error; - } - - break; - - default: - goto error; - } - - break; - - case SW_ARG3_LEN: - if (token == NULL) { - if (ch != '$') { - goto error; - } - rlen = 0; - token = p; - } else if (isdigit(ch)) { - rlen = rlen * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - if ((p - token) <= 1 || rnarg == 0) { - goto error; - } - rnarg--; - token = NULL; - state = SW_ARG3_LEN_LF; - } else { - goto error; - } - - break; - - case SW_ARG3_LEN_LF: - switch (ch) { - case LF: - state = SW_ARG3; - break; - - default: - goto error; - } - - break; - - case SW_ARG3: - m = p + rlen; - if (m >= cmd_end) { - //rlen -= (uint32_t)(b->last - p); - //m = b->last - 1; - //p = m; - //break; - goto error; - } - - if (*m != CR) { - goto error; - } - - p = m; /* move forward by rlen bytes */ - rlen = 0; - state = SW_ARG3_LF; - - break; - - case SW_ARG3_LF: - switch (ch) { - case LF: - if (redis_arg3(r)) { - if (rnarg != 0) { - goto error; - } - goto done; - } else if (redis_argn(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_ARGN_LEN; - } else { - goto error; - } - - break; - - default: - goto error; - } - - break; - - case SW_ARGN_LEN: - if (token == NULL) { - if (ch != '$') { - goto error; - } - rlen = 0; - token = p; - } else if (isdigit(ch)) { - rlen = rlen * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - if ((p - token) <= 1 || rnarg == 0) { - goto error; - } - rnarg--; - token = NULL; - state = SW_ARGN_LEN_LF; - } else { - goto error; - } - - break; - - case SW_ARGN_LEN_LF: - switch (ch) { - case LF: - state = SW_ARGN; - break; - - default: - goto error; - } - - break; - - case SW_ARGN: - m = p + rlen; - if (m >= cmd_end) { - //rlen -= (uint32_t)(b->last - p); - //m = b->last - 1; - //p = m; - //break; - goto error; - } - - if (*m != CR) { - goto error; - } - - p = m; /* move forward by rlen bytes */ - rlen = 0; - state = SW_ARGN_LF; - - break; - - case SW_ARGN_LF: - switch (ch) { - case LF: - if (redis_argn(r) || redis_argeval(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_ARGN_LEN; - } else { - goto error; - } - - break; - - default: - goto error; - } - - break; - - case SW_SENTINEL: - default: - NOT_REACHED(); - break; - } - } - - ASSERT(p == cmd_end); - - return; - -done: - - ASSERT(r->type > CMD_UNKNOWN && r->type < CMD_SENTINEL); - - r->result = CMD_PARSE_OK; - - return; - -enomem: - - r->result = CMD_PARSE_ENOMEM; - - return; - -error: - - r->result = CMD_PARSE_ERROR; - errno = EINVAL; - if(r->errstr == NULL){ - r->errstr = hi_alloc(100*sizeof(*r->errstr)); - } - - len = _scnprintf(r->errstr, 100, "Parse command error. Cmd type: %d, state: %d, break position: %d.", - r->type, state, (int)(p - r->cmd)); - r->errstr[len] = '\0'; -} - -struct cmd *command_get() -{ - struct cmd *command; - command = hi_alloc(sizeof(struct cmd)); - if(command == NULL) - { - return NULL; - } - - command->id = ++cmd_id; - command->result = CMD_PARSE_OK; - command->errstr = NULL; - command->type = CMD_UNKNOWN; - command->cmd = NULL; - command->clen = 0; - command->keys = NULL; - command->narg_start = NULL; - command->narg_end = NULL; - command->narg = 0; - command->quit = 0; - command->noforward = 0; - command->slot_num = -1; - command->frag_seq = NULL; - command->reply = NULL; - command->sub_commands = NULL; - - command->keys = hiarray_create(1, sizeof(struct keypos)); - if (command->keys == NULL) - { - hi_free(command); - return NULL; - } - - return command; -} - -void command_destroy(struct cmd *command) -{ - if(command == NULL) - { - return; - } - - if(command->cmd != NULL) - { - free(command->cmd); - } - - if(command->errstr != NULL){ - hi_free(command->errstr); - } - - if(command->keys != NULL) - { - command->keys->nelem = 0; - hiarray_destroy(command->keys); - } - - if(command->frag_seq != NULL) - { - hi_free(command->frag_seq); - command->frag_seq = NULL; - } - - if(command->reply != NULL) - { - freeReplyObject(command->reply); - } - - if(command->sub_commands != NULL) - { - listRelease(command->sub_commands); - } - - hi_free(command); -} - - diff --git a/ext/hiredis-vip-0.3.0/command.h b/ext/hiredis-vip-0.3.0/command.h deleted file mode 100644 index b7c388a69..000000000 --- a/ext/hiredis-vip-0.3.0/command.h +++ /dev/null @@ -1,179 +0,0 @@ -#ifndef __COMMAND_H_ -#define __COMMAND_H_ - -#include - -#include "hiredis.h" -#include "adlist.h" - -typedef enum cmd_parse_result { - CMD_PARSE_OK, /* parsing ok */ - CMD_PARSE_ENOMEM, /* out of memory */ - CMD_PARSE_ERROR, /* parsing error */ - CMD_PARSE_REPAIR, /* more to parse -> repair parsed & unparsed data */ - CMD_PARSE_AGAIN, /* incomplete -> parse again */ -} cmd_parse_result_t; - -#define CMD_TYPE_CODEC(ACTION) \ - ACTION( UNKNOWN ) \ - ACTION( REQ_REDIS_DEL ) /* redis commands - keys */ \ - ACTION( REQ_REDIS_EXISTS ) \ - ACTION( REQ_REDIS_EXPIRE ) \ - ACTION( REQ_REDIS_EXPIREAT ) \ - ACTION( REQ_REDIS_PEXPIRE ) \ - ACTION( REQ_REDIS_PEXPIREAT ) \ - ACTION( REQ_REDIS_PERSIST ) \ - ACTION( REQ_REDIS_PTTL ) \ - ACTION( REQ_REDIS_SORT ) \ - ACTION( REQ_REDIS_TTL ) \ - ACTION( REQ_REDIS_TYPE ) \ - ACTION( REQ_REDIS_APPEND ) /* redis requests - string */ \ - ACTION( REQ_REDIS_BITCOUNT ) \ - ACTION( REQ_REDIS_DECR ) \ - ACTION( REQ_REDIS_DECRBY ) \ - ACTION( REQ_REDIS_DUMP ) \ - ACTION( REQ_REDIS_GET ) \ - ACTION( REQ_REDIS_GETBIT ) \ - ACTION( REQ_REDIS_GETRANGE ) \ - ACTION( REQ_REDIS_GETSET ) \ - ACTION( REQ_REDIS_INCR ) \ - ACTION( REQ_REDIS_INCRBY ) \ - ACTION( REQ_REDIS_INCRBYFLOAT ) \ - ACTION( REQ_REDIS_MGET ) \ - ACTION( REQ_REDIS_MSET ) \ - ACTION( REQ_REDIS_PSETEX ) \ - ACTION( REQ_REDIS_RESTORE ) \ - ACTION( REQ_REDIS_SET ) \ - ACTION( REQ_REDIS_SETBIT ) \ - ACTION( REQ_REDIS_SETEX ) \ - ACTION( REQ_REDIS_SETNX ) \ - ACTION( REQ_REDIS_SETRANGE ) \ - ACTION( REQ_REDIS_STRLEN ) \ - ACTION( REQ_REDIS_HDEL ) /* redis requests - hashes */ \ - ACTION( REQ_REDIS_HEXISTS ) \ - ACTION( REQ_REDIS_HGET ) \ - ACTION( REQ_REDIS_HGETALL ) \ - ACTION( REQ_REDIS_HINCRBY ) \ - ACTION( REQ_REDIS_HINCRBYFLOAT ) \ - ACTION( REQ_REDIS_HKEYS ) \ - ACTION( REQ_REDIS_HLEN ) \ - ACTION( REQ_REDIS_HMGET ) \ - ACTION( REQ_REDIS_HMSET ) \ - ACTION( REQ_REDIS_HSET ) \ - ACTION( REQ_REDIS_HSETNX ) \ - ACTION( REQ_REDIS_HSCAN) \ - ACTION( REQ_REDIS_HVALS ) \ - ACTION( REQ_REDIS_LINDEX ) /* redis requests - lists */ \ - ACTION( REQ_REDIS_LINSERT ) \ - ACTION( REQ_REDIS_LLEN ) \ - ACTION( REQ_REDIS_LPOP ) \ - ACTION( REQ_REDIS_LPUSH ) \ - ACTION( REQ_REDIS_LPUSHX ) \ - ACTION( REQ_REDIS_LRANGE ) \ - ACTION( REQ_REDIS_LREM ) \ - ACTION( REQ_REDIS_LSET ) \ - ACTION( REQ_REDIS_LTRIM ) \ - ACTION( REQ_REDIS_PFADD ) /* redis requests - hyperloglog */ \ - ACTION( REQ_REDIS_PFCOUNT ) \ - ACTION( REQ_REDIS_PFMERGE ) \ - ACTION( REQ_REDIS_RPOP ) \ - ACTION( REQ_REDIS_RPOPLPUSH ) \ - ACTION( REQ_REDIS_RPUSH ) \ - ACTION( REQ_REDIS_RPUSHX ) \ - ACTION( REQ_REDIS_SADD ) /* redis requests - sets */ \ - ACTION( REQ_REDIS_SCARD ) \ - ACTION( REQ_REDIS_SDIFF ) \ - ACTION( REQ_REDIS_SDIFFSTORE ) \ - ACTION( REQ_REDIS_SINTER ) \ - ACTION( REQ_REDIS_SINTERSTORE ) \ - ACTION( REQ_REDIS_SISMEMBER ) \ - ACTION( REQ_REDIS_SMEMBERS ) \ - ACTION( REQ_REDIS_SMOVE ) \ - ACTION( REQ_REDIS_SPOP ) \ - ACTION( REQ_REDIS_SRANDMEMBER ) \ - ACTION( REQ_REDIS_SREM ) \ - ACTION( REQ_REDIS_SUNION ) \ - ACTION( REQ_REDIS_SUNIONSTORE ) \ - ACTION( REQ_REDIS_SSCAN) \ - ACTION( REQ_REDIS_ZADD ) /* redis requests - sorted sets */ \ - ACTION( REQ_REDIS_ZCARD ) \ - ACTION( REQ_REDIS_ZCOUNT ) \ - ACTION( REQ_REDIS_ZINCRBY ) \ - ACTION( REQ_REDIS_ZINTERSTORE ) \ - ACTION( REQ_REDIS_ZLEXCOUNT ) \ - ACTION( REQ_REDIS_ZRANGE ) \ - ACTION( REQ_REDIS_ZRANGEBYLEX ) \ - ACTION( REQ_REDIS_ZRANGEBYSCORE ) \ - ACTION( REQ_REDIS_ZRANK ) \ - ACTION( REQ_REDIS_ZREM ) \ - ACTION( REQ_REDIS_ZREMRANGEBYRANK ) \ - ACTION( REQ_REDIS_ZREMRANGEBYLEX ) \ - ACTION( REQ_REDIS_ZREMRANGEBYSCORE ) \ - ACTION( REQ_REDIS_ZREVRANGE ) \ - ACTION( REQ_REDIS_ZREVRANGEBYSCORE ) \ - ACTION( REQ_REDIS_ZREVRANK ) \ - ACTION( REQ_REDIS_ZSCORE ) \ - ACTION( REQ_REDIS_ZUNIONSTORE ) \ - ACTION( REQ_REDIS_ZSCAN) \ - ACTION( REQ_REDIS_EVAL ) /* redis requests - eval */ \ - ACTION( REQ_REDIS_EVALSHA ) \ - ACTION( REQ_REDIS_PING ) /* redis requests - ping/quit */ \ - ACTION( REQ_REDIS_QUIT) \ - ACTION( REQ_REDIS_AUTH) \ - ACTION( RSP_REDIS_STATUS ) /* redis response */ \ - ACTION( RSP_REDIS_ERROR ) \ - ACTION( RSP_REDIS_INTEGER ) \ - ACTION( RSP_REDIS_BULK ) \ - ACTION( RSP_REDIS_MULTIBULK ) \ - ACTION( SENTINEL ) \ - - -#define DEFINE_ACTION(_name) CMD_##_name, -typedef enum cmd_type { - CMD_TYPE_CODEC(DEFINE_ACTION) -} cmd_type_t; -#undef DEFINE_ACTION - - -struct keypos { - char *start; /* key start pos */ - char *end; /* key end pos */ - uint32_t remain_len; /* remain length after keypos->end for more key-value pairs in command, like mset */ -}; - -struct cmd { - - uint64_t id; /* command id */ - - cmd_parse_result_t result; /* command parsing result */ - char *errstr; /* error info when the command parse failed */ - - cmd_type_t type; /* command type */ - - char *cmd; - uint32_t clen; /* command length */ - - struct hiarray *keys; /* array of keypos, for req */ - - char *narg_start; /* narg start (redis) */ - char *narg_end; /* narg end (redis) */ - uint32_t narg; /* # arguments (redis) */ - - unsigned quit:1; /* quit request? */ - unsigned noforward:1; /* not need forward (example: ping) */ - - int slot_num; /* this command should send to witch slot? - * -1:the keys in this command cross different slots*/ - struct cmd **frag_seq; /* sequence of fragment command, map from keys to fragments*/ - - redisReply *reply; - - hilist *sub_commands; /* just for pipeline and multi-key commands */ -}; - -void redis_parse_cmd(struct cmd *r); - -struct cmd *command_get(void); -void command_destroy(struct cmd *command); - -#endif diff --git a/ext/hiredis-vip-0.3.0/crc16.c b/ext/hiredis-vip-0.3.0/crc16.c deleted file mode 100644 index 0f304f6e4..000000000 --- a/ext/hiredis-vip-0.3.0/crc16.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2001-2010 Georges Menie (www.menie.org) - * Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style) - * 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 the University of California, Berkeley 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 REGENTS 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 REGENTS AND 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. - */ - -/* CRC16 implementation according to CCITT standards. - * - * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the - * following parameters: - * - * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" - * Width : 16 bit - * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) - * Initialization : 0000 - * Reflect Input byte : False - * Reflect Output CRC : False - * Xor constant to output CRC : 0000 - * Output for "123456789" : 31C3 - */ -#include "hiutil.h" - -static const uint16_t crc16tab[256]= { - 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, - 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, - 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, - 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, - 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, - 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, - 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, - 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, - 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, - 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, - 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, - 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, - 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, - 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, - 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, - 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, - 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, - 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, - 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, - 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, - 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, - 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, - 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, - 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, - 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, - 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, - 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, - 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, - 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, - 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, - 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, - 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 -}; - -uint16_t crc16(const char *buf, int len) { - int counter; - uint16_t crc = 0; - for (counter = 0; counter < len; counter++) - crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF]; - return crc; -} diff --git a/ext/hiredis-vip-0.3.0/dict.c b/ext/hiredis-vip-0.3.0/dict.c deleted file mode 100644 index 79b1041ca..000000000 --- a/ext/hiredis-vip-0.3.0/dict.c +++ /dev/null @@ -1,338 +0,0 @@ -/* Hash table implementation. - * - * This file implements in memory hash tables with insert/del/replace/find/ - * get-random-element operations. Hash tables will auto resize if needed - * tables of power of two in size are used, collisions are handled by - * chaining. See the source code for more information... :) - * - * Copyright (c) 2006-2010, Salvatore Sanfilippo - * 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 "fmacros.h" -#include -#include -#include -#include "dict.h" - -/* -------------------------- private prototypes ---------------------------- */ - -static int _dictExpandIfNeeded(dict *ht); -static unsigned long _dictNextPower(unsigned long size); -static int _dictKeyIndex(dict *ht, const void *key); -static int _dictInit(dict *ht, dictType *type, void *privDataPtr); - -/* -------------------------- hash functions -------------------------------- */ - -/* Generic hash function (a popular one from Bernstein). - * I tested a few and this was the best. */ -static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { - unsigned int hash = 5381; - - while (len--) - hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ - return hash; -} - -/* ----------------------------- API implementation ------------------------- */ - -/* Reset an hashtable already initialized with ht_init(). - * NOTE: This function should only called by ht_destroy(). */ -static void _dictReset(dict *ht) { - ht->table = NULL; - ht->size = 0; - ht->sizemask = 0; - ht->used = 0; -} - -/* Create a new hash table */ -static dict *dictCreate(dictType *type, void *privDataPtr) { - dict *ht = malloc(sizeof(*ht)); - _dictInit(ht,type,privDataPtr); - return ht; -} - -/* Initialize the hash table */ -static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { - _dictReset(ht); - ht->type = type; - ht->privdata = privDataPtr; - return DICT_OK; -} - -/* Expand or create the hashtable */ -static int dictExpand(dict *ht, unsigned long size) { - dict n; /* the new hashtable */ - unsigned long realsize = _dictNextPower(size), i; - - /* the size is invalid if it is smaller than the number of - * elements already inside the hashtable */ - if (ht->used > size) - return DICT_ERR; - - _dictInit(&n, ht->type, ht->privdata); - n.size = realsize; - n.sizemask = realsize-1; - n.table = calloc(realsize,sizeof(dictEntry*)); - - /* Copy all the elements from the old to the new table: - * note that if the old hash table is empty ht->size is zero, - * so dictExpand just creates an hash table. */ - n.used = ht->used; - for (i = 0; i < ht->size && ht->used > 0; i++) { - dictEntry *he, *nextHe; - - if (ht->table[i] == NULL) continue; - - /* For each hash entry on this slot... */ - he = ht->table[i]; - while(he) { - unsigned int h; - - nextHe = he->next; - /* Get the new element index */ - h = dictHashKey(ht, he->key) & n.sizemask; - he->next = n.table[h]; - n.table[h] = he; - ht->used--; - /* Pass to the next element */ - he = nextHe; - } - } - assert(ht->used == 0); - free(ht->table); - - /* Remap the new hashtable in the old */ - *ht = n; - return DICT_OK; -} - -/* Add an element to the target hash table */ -static int dictAdd(dict *ht, void *key, void *val) { - int index; - dictEntry *entry; - - /* Get the index of the new element, or -1 if - * the element already exists. */ - if ((index = _dictKeyIndex(ht, key)) == -1) - return DICT_ERR; - - /* Allocates the memory and stores key */ - entry = malloc(sizeof(*entry)); - entry->next = ht->table[index]; - ht->table[index] = entry; - - /* Set the hash entry fields. */ - dictSetHashKey(ht, entry, key); - dictSetHashVal(ht, entry, val); - ht->used++; - return DICT_OK; -} - -/* Add an element, discarding the old if the key already exists. - * Return 1 if the key was added from scratch, 0 if there was already an - * element with such key and dictReplace() just performed a value update - * operation. */ -static int dictReplace(dict *ht, void *key, void *val) { - dictEntry *entry, auxentry; - - /* Try to add the element. If the key - * does not exists dictAdd will suceed. */ - if (dictAdd(ht, key, val) == DICT_OK) - return 1; - /* It already exists, get the entry */ - entry = dictFind(ht, key); - /* Free the old value and set the new one */ - /* Set the new value and free the old one. Note that it is important - * to do that in this order, as the value may just be exactly the same - * as the previous one. In this context, think to reference counting, - * you want to increment (set), and then decrement (free), and not the - * reverse. */ - auxentry = *entry; - dictSetHashVal(ht, entry, val); - dictFreeEntryVal(ht, &auxentry); - return 0; -} - -/* Search and remove an element */ -static int dictDelete(dict *ht, const void *key) { - unsigned int h; - dictEntry *de, *prevde; - - if (ht->size == 0) - return DICT_ERR; - h = dictHashKey(ht, key) & ht->sizemask; - de = ht->table[h]; - - prevde = NULL; - while(de) { - if (dictCompareHashKeys(ht,key,de->key)) { - /* Unlink the element from the list */ - if (prevde) - prevde->next = de->next; - else - ht->table[h] = de->next; - - dictFreeEntryKey(ht,de); - dictFreeEntryVal(ht,de); - free(de); - ht->used--; - return DICT_OK; - } - prevde = de; - de = de->next; - } - return DICT_ERR; /* not found */ -} - -/* Destroy an entire hash table */ -static int _dictClear(dict *ht) { - unsigned long i; - - /* Free all the elements */ - for (i = 0; i < ht->size && ht->used > 0; i++) { - dictEntry *he, *nextHe; - - if ((he = ht->table[i]) == NULL) continue; - while(he) { - nextHe = he->next; - dictFreeEntryKey(ht, he); - dictFreeEntryVal(ht, he); - free(he); - ht->used--; - he = nextHe; - } - } - /* Free the table and the allocated cache structure */ - free(ht->table); - /* Re-initialize the table */ - _dictReset(ht); - return DICT_OK; /* never fails */ -} - -/* Clear & Release the hash table */ -static void dictRelease(dict *ht) { - _dictClear(ht); - free(ht); -} - -static dictEntry *dictFind(dict *ht, const void *key) { - dictEntry *he; - unsigned int h; - - if (ht->size == 0) return NULL; - h = dictHashKey(ht, key) & ht->sizemask; - he = ht->table[h]; - while(he) { - if (dictCompareHashKeys(ht, key, he->key)) - return he; - he = he->next; - } - return NULL; -} - -static dictIterator *dictGetIterator(dict *ht) { - dictIterator *iter = malloc(sizeof(*iter)); - - iter->ht = ht; - iter->index = -1; - iter->entry = NULL; - iter->nextEntry = NULL; - return iter; -} - -static dictEntry *dictNext(dictIterator *iter) { - while (1) { - if (iter->entry == NULL) { - iter->index++; - if (iter->index >= - (signed)iter->ht->size) break; - iter->entry = iter->ht->table[iter->index]; - } else { - iter->entry = iter->nextEntry; - } - if (iter->entry) { - /* We need to save the 'next' here, the iterator user - * may delete the entry we are returning. */ - iter->nextEntry = iter->entry->next; - return iter->entry; - } - } - return NULL; -} - -static void dictReleaseIterator(dictIterator *iter) { - free(iter); -} - -/* ------------------------- private functions ------------------------------ */ - -/* Expand the hash table if needed */ -static int _dictExpandIfNeeded(dict *ht) { - /* If the hash table is empty expand it to the intial size, - * if the table is "full" dobule its size. */ - if (ht->size == 0) - return dictExpand(ht, DICT_HT_INITIAL_SIZE); - if (ht->used == ht->size) - return dictExpand(ht, ht->size*2); - return DICT_OK; -} - -/* Our hash table capability is a power of two */ -static unsigned long _dictNextPower(unsigned long size) { - unsigned long i = DICT_HT_INITIAL_SIZE; - - if (size >= LONG_MAX) return LONG_MAX; - while(1) { - if (i >= size) - return i; - i *= 2; - } -} - -/* Returns the index of a free slot that can be populated with - * an hash entry for the given 'key'. - * If the key already exists, -1 is returned. */ -static int _dictKeyIndex(dict *ht, const void *key) { - unsigned int h; - dictEntry *he; - - /* Expand the hashtable if needed */ - if (_dictExpandIfNeeded(ht) == DICT_ERR) - return -1; - /* Compute the key hash value */ - h = dictHashKey(ht, key) & ht->sizemask; - /* Search if this slot does not already contain the given key */ - he = ht->table[h]; - while(he) { - if (dictCompareHashKeys(ht, key, he->key)) - return -1; - he = he->next; - } - return h; -} - diff --git a/ext/hiredis-vip-0.3.0/examples/example-ae.c b/ext/hiredis-vip-0.3.0/examples/example-ae.c deleted file mode 100644 index 8efa7306a..000000000 --- a/ext/hiredis-vip-0.3.0/examples/example-ae.c +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -/* Put event loop in the global scope, so it can be explicitly stopped */ -static aeEventLoop *loop; - -void getCallback(redisAsyncContext *c, void *r, void *privdata) { - redisReply *reply = r; - if (reply == NULL) return; - printf("argv[%s]: %s\n", (char*)privdata, reply->str); - - /* Disconnect after receiving the reply to GET */ - redisAsyncDisconnect(c); -} - -void connectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - aeStop(loop); - return; - } - - printf("Connected...\n"); -} - -void disconnectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - aeStop(loop); - return; - } - - printf("Disconnected...\n"); - aeStop(loop); -} - -int main (int argc, char **argv) { - signal(SIGPIPE, SIG_IGN); - - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); - if (c->err) { - /* Let *c leak for now... */ - printf("Error: %s\n", c->errstr); - return 1; - } - - loop = aeCreateEventLoop(64); - redisAeAttach(loop, c); - redisAsyncSetConnectCallback(c,connectCallback); - redisAsyncSetDisconnectCallback(c,disconnectCallback); - redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); - redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); - aeMain(loop); - return 0; -} - diff --git a/ext/hiredis-vip-0.3.0/examples/example-glib.c b/ext/hiredis-vip-0.3.0/examples/example-glib.c deleted file mode 100644 index d6e10f8e8..000000000 --- a/ext/hiredis-vip-0.3.0/examples/example-glib.c +++ /dev/null @@ -1,73 +0,0 @@ -#include - -#include -#include -#include - -static GMainLoop *mainloop; - -static void -connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, - int status) -{ - if (status != REDIS_OK) { - g_printerr("Failed to connect: %s\n", ac->errstr); - g_main_loop_quit(mainloop); - } else { - g_printerr("Connected...\n"); - } -} - -static void -disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, - int status) -{ - if (status != REDIS_OK) { - g_error("Failed to disconnect: %s", ac->errstr); - } else { - g_printerr("Disconnected...\n"); - g_main_loop_quit(mainloop); - } -} - -static void -command_cb(redisAsyncContext *ac, - gpointer r, - gpointer user_data G_GNUC_UNUSED) -{ - redisReply *reply = r; - - if (reply) { - g_print("REPLY: %s\n", reply->str); - } - - redisAsyncDisconnect(ac); -} - -gint -main (gint argc G_GNUC_UNUSED, - gchar *argv[] G_GNUC_UNUSED) -{ - redisAsyncContext *ac; - GMainContext *context = NULL; - GSource *source; - - ac = redisAsyncConnect("127.0.0.1", 6379); - if (ac->err) { - g_printerr("%s\n", ac->errstr); - exit(EXIT_FAILURE); - } - - source = redis_source_new(ac); - mainloop = g_main_loop_new(context, FALSE); - g_source_attach(source, context); - - redisAsyncSetConnectCallback(ac, connect_cb); - redisAsyncSetDisconnectCallback(ac, disconnect_cb); - redisAsyncCommand(ac, command_cb, NULL, "SET key 1234"); - redisAsyncCommand(ac, command_cb, NULL, "GET key"); - - g_main_loop_run(mainloop); - - return EXIT_SUCCESS; -} diff --git a/ext/hiredis-vip-0.3.0/examples/example-libev.c b/ext/hiredis-vip-0.3.0/examples/example-libev.c deleted file mode 100644 index cc8b166ec..000000000 --- a/ext/hiredis-vip-0.3.0/examples/example-libev.c +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -void getCallback(redisAsyncContext *c, void *r, void *privdata) { - redisReply *reply = r; - if (reply == NULL) return; - printf("argv[%s]: %s\n", (char*)privdata, reply->str); - - /* Disconnect after receiving the reply to GET */ - redisAsyncDisconnect(c); -} - -void connectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - return; - } - printf("Connected...\n"); -} - -void disconnectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - return; - } - printf("Disconnected...\n"); -} - -int main (int argc, char **argv) { - signal(SIGPIPE, SIG_IGN); - - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); - if (c->err) { - /* Let *c leak for now... */ - printf("Error: %s\n", c->errstr); - return 1; - } - - redisLibevAttach(EV_DEFAULT_ c); - redisAsyncSetConnectCallback(c,connectCallback); - redisAsyncSetDisconnectCallback(c,disconnectCallback); - redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); - redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); - ev_loop(EV_DEFAULT_ 0); - return 0; -} diff --git a/ext/hiredis-vip-0.3.0/examples/example-libevent.c b/ext/hiredis-vip-0.3.0/examples/example-libevent.c deleted file mode 100644 index d333c22b7..000000000 --- a/ext/hiredis-vip-0.3.0/examples/example-libevent.c +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -void getCallback(redisAsyncContext *c, void *r, void *privdata) { - redisReply *reply = r; - if (reply == NULL) return; - printf("argv[%s]: %s\n", (char*)privdata, reply->str); - - /* Disconnect after receiving the reply to GET */ - redisAsyncDisconnect(c); -} - -void connectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - return; - } - printf("Connected...\n"); -} - -void disconnectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - return; - } - printf("Disconnected...\n"); -} - -int main (int argc, char **argv) { - signal(SIGPIPE, SIG_IGN); - struct event_base *base = event_base_new(); - - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); - if (c->err) { - /* Let *c leak for now... */ - printf("Error: %s\n", c->errstr); - return 1; - } - - redisLibeventAttach(c,base); - redisAsyncSetConnectCallback(c,connectCallback); - redisAsyncSetDisconnectCallback(c,disconnectCallback); - redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); - redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); - event_base_dispatch(base); - return 0; -} diff --git a/ext/hiredis-vip-0.3.0/examples/example-libuv.c b/ext/hiredis-vip-0.3.0/examples/example-libuv.c deleted file mode 100644 index a5462d410..000000000 --- a/ext/hiredis-vip-0.3.0/examples/example-libuv.c +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -void getCallback(redisAsyncContext *c, void *r, void *privdata) { - redisReply *reply = r; - if (reply == NULL) return; - printf("argv[%s]: %s\n", (char*)privdata, reply->str); - - /* Disconnect after receiving the reply to GET */ - redisAsyncDisconnect(c); -} - -void connectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - return; - } - printf("Connected...\n"); -} - -void disconnectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - return; - } - printf("Disconnected...\n"); -} - -int main (int argc, char **argv) { - signal(SIGPIPE, SIG_IGN); - uv_loop_t* loop = uv_default_loop(); - - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); - if (c->err) { - /* Let *c leak for now... */ - printf("Error: %s\n", c->errstr); - return 1; - } - - redisLibuvAttach(c,loop); - redisAsyncSetConnectCallback(c,connectCallback); - redisAsyncSetDisconnectCallback(c,disconnectCallback); - redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); - redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); - uv_run(loop, UV_RUN_DEFAULT); - return 0; -} diff --git a/ext/hiredis-vip-0.3.0/examples/example.c b/ext/hiredis-vip-0.3.0/examples/example.c deleted file mode 100644 index 25226a807..000000000 --- a/ext/hiredis-vip-0.3.0/examples/example.c +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include - -#include - -int main(int argc, char **argv) { - unsigned int j; - redisContext *c; - redisReply *reply; - const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; - int port = (argc > 2) ? atoi(argv[2]) : 6379; - - struct timeval timeout = { 1, 500000 }; // 1.5 seconds - c = redisConnectWithTimeout(hostname, port, timeout); - if (c == NULL || c->err) { - if (c) { - printf("Connection error: %s\n", c->errstr); - redisFree(c); - } else { - printf("Connection error: can't allocate redis context\n"); - } - exit(1); - } - - /* PING server */ - reply = redisCommand(c,"PING"); - printf("PING: %s\n", reply->str); - freeReplyObject(reply); - - /* Set a key */ - reply = redisCommand(c,"SET %s %s", "foo", "hello world"); - printf("SET: %s\n", reply->str); - freeReplyObject(reply); - - /* Set a key using binary safe API */ - reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); - printf("SET (binary API): %s\n", reply->str); - freeReplyObject(reply); - - /* Try a GET and two INCR */ - reply = redisCommand(c,"GET foo"); - printf("GET foo: %s\n", reply->str); - freeReplyObject(reply); - - reply = redisCommand(c,"INCR counter"); - printf("INCR counter: %lld\n", reply->integer); - freeReplyObject(reply); - /* again ... */ - reply = redisCommand(c,"INCR counter"); - printf("INCR counter: %lld\n", reply->integer); - freeReplyObject(reply); - - /* Create a list of numbers, from 0 to 9 */ - reply = redisCommand(c,"DEL mylist"); - freeReplyObject(reply); - for (j = 0; j < 10; j++) { - char buf[64]; - - snprintf(buf,64,"%d",j); - reply = redisCommand(c,"LPUSH mylist element-%s", buf); - freeReplyObject(reply); - } - - /* Let's check what we have inside the list */ - reply = redisCommand(c,"LRANGE mylist 0 -1"); - if (reply->type == REDIS_REPLY_ARRAY) { - for (j = 0; j < reply->elements; j++) { - printf("%u) %s\n", j, reply->element[j]->str); - } - } - freeReplyObject(reply); - - /* Disconnects and frees the context */ - redisFree(c); - - return 0; -} diff --git a/ext/hiredis-vip-0.3.0/fmacros.h b/ext/hiredis-vip-0.3.0/fmacros.h deleted file mode 100644 index a3b1df034..000000000 --- a/ext/hiredis-vip-0.3.0/fmacros.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef __HIREDIS_FMACRO_H -#define __HIREDIS_FMACRO_H - -#if defined(__linux__) -#ifndef _BSD_SOURCE -#define _BSD_SOURCE -#endif -#define _DEFAULT_SOURCE -#endif - -#if defined(__sun__) -#define _POSIX_C_SOURCE 200112L -#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__) -#define _XOPEN_SOURCE 600 -#else -#define _XOPEN_SOURCE -#endif - -#if __APPLE__ && __MACH__ -#define _OSX -#endif - -#endif diff --git a/ext/hiredis-vip-0.3.0/hiarray.c b/ext/hiredis-vip-0.3.0/hiarray.c deleted file mode 100644 index cf742ecf6..000000000 --- a/ext/hiredis-vip-0.3.0/hiarray.c +++ /dev/null @@ -1,188 +0,0 @@ -#include - -#include "hiutil.h" -#include "hiarray.h" - -struct hiarray * -hiarray_create(uint32_t n, size_t size) -{ - struct hiarray *a; - - ASSERT(n != 0 && size != 0); - - a = hi_alloc(sizeof(*a)); - if (a == NULL) { - return NULL; - } - - a->elem = hi_alloc(n * size); - if (a->elem == NULL) { - hi_free(a); - return NULL; - } - - a->nelem = 0; - a->size = size; - a->nalloc = n; - - return a; -} - -void -hiarray_destroy(struct hiarray *a) -{ - hiarray_deinit(a); - hi_free(a); -} - -int -hiarray_init(struct hiarray *a, uint32_t n, size_t size) -{ - ASSERT(n != 0 && size != 0); - - a->elem = hi_alloc(n * size); - if (a->elem == NULL) { - return HI_ENOMEM; - } - - a->nelem = 0; - a->size = size; - a->nalloc = n; - - return HI_OK; -} - -void -hiarray_deinit(struct hiarray *a) -{ - ASSERT(a->nelem == 0); - - if (a->elem != NULL) { - hi_free(a->elem); - } -} - -uint32_t -hiarray_idx(struct hiarray *a, void *elem) -{ - uint8_t *p, *q; - uint32_t off, idx; - - ASSERT(elem >= a->elem); - - p = a->elem; - q = elem; - off = (uint32_t)(q - p); - - ASSERT(off % (uint32_t)a->size == 0); - - idx = off / (uint32_t)a->size; - - return idx; -} - -void * -hiarray_push(struct hiarray *a) -{ - void *elem, *new; - size_t size; - - if (a->nelem == a->nalloc) { - - /* the array is full; allocate new array */ - size = a->size * a->nalloc; - new = hi_realloc(a->elem, 2 * size); - if (new == NULL) { - return NULL; - } - - a->elem = new; - a->nalloc *= 2; - } - - elem = (uint8_t *)a->elem + a->size * a->nelem; - a->nelem++; - - return elem; -} - -void * -hiarray_pop(struct hiarray *a) -{ - void *elem; - - ASSERT(a->nelem != 0); - - a->nelem--; - elem = (uint8_t *)a->elem + a->size * a->nelem; - - return elem; -} - -void * -hiarray_get(struct hiarray *a, uint32_t idx) -{ - void *elem; - - ASSERT(a->nelem != 0); - ASSERT(idx < a->nelem); - - elem = (uint8_t *)a->elem + (a->size * idx); - - return elem; -} - -void * -hiarray_top(struct hiarray *a) -{ - ASSERT(a->nelem != 0); - - return hiarray_get(a, a->nelem - 1); -} - -void -hiarray_swap(struct hiarray *a, struct hiarray *b) -{ - struct hiarray tmp; - - tmp = *a; - *a = *b; - *b = tmp; -} - -/* - * Sort nelem elements of the array in ascending order based on the - * compare comparator. - */ -void -hiarray_sort(struct hiarray *a, hiarray_compare_t compare) -{ - ASSERT(a->nelem != 0); - - qsort(a->elem, a->nelem, a->size, compare); -} - -/* - * Calls the func once for each element in the array as long as func returns - * success. On failure short-circuits and returns the error status. - */ -int -hiarray_each(struct hiarray *a, hiarray_each_t func, void *data) -{ - uint32_t i, nelem; - - ASSERT(array_n(a) != 0); - ASSERT(func != NULL); - - for (i = 0, nelem = hiarray_n(a); i < nelem; i++) { - void *elem = hiarray_get(a, i); - rstatus_t status; - - status = func(elem, data); - if (status != HI_OK) { - return status; - } - } - - return HI_OK; -} diff --git a/ext/hiredis-vip-0.3.0/hiarray.h b/ext/hiredis-vip-0.3.0/hiarray.h deleted file mode 100644 index fda3a4b8b..000000000 --- a/ext/hiredis-vip-0.3.0/hiarray.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef __HIARRAY_H_ -#define __HIARRAY_H_ - -#include - -typedef int (*hiarray_compare_t)(const void *, const void *); -typedef int (*hiarray_each_t)(void *, void *); - -struct hiarray { - uint32_t nelem; /* # element */ - void *elem; /* element */ - size_t size; /* element size */ - uint32_t nalloc; /* # allocated element */ -}; - -#define null_hiarray { 0, NULL, 0, 0 } - -static inline void -hiarray_null(struct hiarray *a) -{ - a->nelem = 0; - a->elem = NULL; - a->size = 0; - a->nalloc = 0; -} - -static inline void -hiarray_set(struct hiarray *a, void *elem, size_t size, uint32_t nalloc) -{ - a->nelem = 0; - a->elem = elem; - a->size = size; - a->nalloc = nalloc; -} - -static inline uint32_t -hiarray_n(const struct hiarray *a) -{ - return a->nelem; -} - -struct hiarray *hiarray_create(uint32_t n, size_t size); -void hiarray_destroy(struct hiarray *a); -int hiarray_init(struct hiarray *a, uint32_t n, size_t size); -void hiarray_deinit(struct hiarray *a); - -uint32_t hiarray_idx(struct hiarray *a, void *elem); -void *hiarray_push(struct hiarray *a); -void *hiarray_pop(struct hiarray *a); -void *hiarray_get(struct hiarray *a, uint32_t idx); -void *hiarray_top(struct hiarray *a); -void hiarray_swap(struct hiarray *a, struct hiarray *b); -void hiarray_sort(struct hiarray *a, hiarray_compare_t compare); -int hiarray_each(struct hiarray *a, hiarray_each_t func, void *data); - -#endif diff --git a/ext/hiredis-vip-0.3.0/hircluster.c b/ext/hiredis-vip-0.3.0/hircluster.c deleted file mode 100644 index edf9cb2f9..000000000 --- a/ext/hiredis-vip-0.3.0/hircluster.c +++ /dev/null @@ -1,4747 +0,0 @@ - -#include "fmacros.h" -#include -#include -#include -#include -#include - -#include "hircluster.h" -#include "hiutil.h" -#include "adlist.h" -#include "hiarray.h" -#include "command.h" -#include "dict.c" - -#define REDIS_COMMAND_CLUSTER_NODES "CLUSTER NODES" -#define REDIS_COMMAND_CLUSTER_SLOTS "CLUSTER SLOTS" - -#define REDIS_COMMAND_ASKING "ASKING" -#define REDIS_COMMAND_PING "PING" - -#define REDIS_PROTOCOL_ASKING "*1\r\n$6\r\nASKING\r\n" - -#define IP_PORT_SEPARATOR ":" - -#define CLUSTER_ADDRESS_SEPARATOR "," - -#define CLUSTER_DEFAULT_MAX_REDIRECT_COUNT 5 - -typedef struct cluster_async_data -{ - redisClusterAsyncContext *acc; - struct cmd *command; - redisClusterCallbackFn *callback; - int retry_count; - void *privdata; -}cluster_async_data; - -typedef enum CLUSTER_ERR_TYPE{ - CLUSTER_NOT_ERR = 0, - CLUSTER_ERR_MOVED, - CLUSTER_ERR_ASK, - CLUSTER_ERR_TRYAGAIN, - CLUSTER_ERR_CROSSSLOT, - CLUSTER_ERR_CLUSTERDOWN, - CLUSTER_ERR_SENTINEL -}CLUSTER_ERR_TYPE; - -static void cluster_node_deinit(cluster_node *node); -static void cluster_slot_destroy(cluster_slot *slot); -static void cluster_open_slot_destroy(copen_slot *oslot); - -void listClusterNodeDestructor(void *val) -{ - cluster_node_deinit(val); - - hi_free(val); -} - -void listClusterSlotDestructor(void *val) -{ - cluster_slot_destroy(val); -} - -unsigned int dictSdsHash(const void *key) { - return dictGenHashFunction((unsigned char*)key, sdslen((char*)key)); -} - -int dictSdsKeyCompare(void *privdata, const void *key1, - const void *key2) -{ - int l1,l2; - DICT_NOTUSED(privdata); - - l1 = sdslen((sds)key1); - l2 = sdslen((sds)key2); - if (l1 != l2) return 0; - return memcmp(key1, key2, l1) == 0; -} - -void dictSdsDestructor(void *privdata, void *val) -{ - DICT_NOTUSED(privdata); - - sdsfree(val); -} - -void dictClusterNodeDestructor(void *privdata, void *val) -{ - DICT_NOTUSED(privdata); - - cluster_node_deinit(val); - - hi_free(val); -} - -/* Cluster nodes hash table, mapping nodes - * name(437c719f50dc9d0745032f3b280ce7ecc40792ac) - * or addresses(1.2.3.4:6379) to clusterNode structures. - * Those nodes need destroy. - */ -dictType clusterNodesDictType = { - dictSdsHash, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - dictSdsKeyCompare, /* key compare */ - dictSdsDestructor, /* key destructor */ - dictClusterNodeDestructor /* val destructor */ -}; - -/* Cluster nodes hash table, mapping nodes - * name(437c719f50dc9d0745032f3b280ce7ecc40792ac) - * or addresses(1.2.3.4:6379) to clusterNode structures. - * Those nodes do not need destroy. - */ -dictType clusterNodesRefDictType = { - dictSdsHash, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - dictSdsKeyCompare, /* key compare */ - dictSdsDestructor, /* key destructor */ - NULL /* val destructor */ -}; - - -void listCommandFree(void *command) -{ - struct cmd *cmd = command; - command_destroy(cmd); -} - -/* Defined in hiredis.c */ -void __redisSetError(redisContext *c, int type, const char *str); - -/* Forward declaration of function in hiredis.c */ -int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); - -/* Helper function for the redisClusterCommand* family of functions. - * - * Write a formatted command to the output buffer. If the given context is - * blocking, immediately read the reply into the "reply" pointer. When the - * context is non-blocking, the "reply" pointer will not be used and the - * command is simply appended to the write buffer. - * - * Returns the reply when a reply was succesfully retrieved. Returns NULL - * otherwise. When NULL is returned in a blocking context, the error field - * in the context will be set. - */ -static void *__redisBlockForReply(redisContext *c) { - void *reply; - - if (c->flags & REDIS_BLOCK) { - if (redisGetReply(c,&reply) != REDIS_OK) - return NULL; - return reply; - } - return NULL; -} - - -/* ----------------------------------------------------------------------------- - * Key space handling - * -------------------------------------------------------------------------- */ - -/* We have 16384 hash slots. The hash slot of a given key is obtained - * as the least significant 14 bits of the crc16 of the key. - * - * However if the key contains the {...} pattern, only the part between - * { and } is hashed. This may be useful in the future to force certain - * keys to be in the same node (assuming no resharding is in progress). */ -static unsigned int keyHashSlot(char *key, int keylen) { - int s, e; /* start-end indexes of { and } */ - - for (s = 0; s < keylen; s++) - if (key[s] == '{') break; - - /* No '{' ? Hash the whole key. This is the base case. */ - if (s == keylen) return crc16(key,keylen) & 0x3FFF; - - /* '{' found? Check if we have the corresponding '}'. */ - for (e = s+1; e < keylen; e++) - if (key[e] == '}') break; - - /* No '}' or nothing betweeen {} ? Hash the whole key. */ - if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF; - - /* If we are here there is both a { and a } on its right. Hash - * what is in the middle between { and }. */ - return crc16(key+s+1,e-s-1) & 0x3FFF; -} - -static void __redisClusterSetError(redisClusterContext *cc, int type, const char *str) { - size_t len; - - if(cc == NULL){ - return; - } - - cc->err = type; - if (str != NULL) { - len = strlen(str); - len = len < (sizeof(cc->errstr)-1) ? len : (sizeof(cc->errstr)-1); - memcpy(cc->errstr,str,len); - cc->errstr[len] = '\0'; - } else { - /* Only REDIS_ERR_IO may lack a description! */ - assert(type == REDIS_ERR_IO); - __redis_strerror_r(errno, cc->errstr, sizeof(cc->errstr)); - } -} - -static int cluster_reply_error_type(redisReply *reply) -{ - - if(reply == NULL) - { - return REDIS_ERR; - } - - if(reply->type == REDIS_REPLY_ERROR) - { - if((int)strlen(REDIS_ERROR_MOVED) < reply->len && - strncmp(reply->str, REDIS_ERROR_MOVED, strlen(REDIS_ERROR_MOVED)) == 0) - { - return CLUSTER_ERR_MOVED; - } - else if((int)strlen(REDIS_ERROR_ASK) < reply->len && - strncmp(reply->str, REDIS_ERROR_ASK, strlen(REDIS_ERROR_ASK)) == 0) - { - return CLUSTER_ERR_ASK; - } - else if((int)strlen(REDIS_ERROR_TRYAGAIN) < reply->len && - strncmp(reply->str, REDIS_ERROR_TRYAGAIN, strlen(REDIS_ERROR_TRYAGAIN)) == 0) - { - return CLUSTER_ERR_TRYAGAIN; - } - else if((int)strlen(REDIS_ERROR_CROSSSLOT) < reply->len && - strncmp(reply->str, REDIS_ERROR_CROSSSLOT, strlen(REDIS_ERROR_CROSSSLOT)) == 0) - { - return CLUSTER_ERR_CROSSSLOT; - } - else if((int)strlen(REDIS_ERROR_CLUSTERDOWN) < reply->len && - strncmp(reply->str, REDIS_ERROR_CLUSTERDOWN, strlen(REDIS_ERROR_CLUSTERDOWN)) == 0) - { - return CLUSTER_ERR_CLUSTERDOWN; - } - else - { - return CLUSTER_ERR_SENTINEL; - } - } - - return CLUSTER_NOT_ERR; -} - -static int cluster_node_init(cluster_node *node) -{ - if(node == NULL){ - return REDIS_ERR; - } - - node->name = NULL; - node->addr = NULL; - node->host = NULL; - node->port = 0; - node->role = REDIS_ROLE_NULL; - node->myself = 0; - node->slaves = NULL; - node->con = NULL; - node->acon = NULL; - node->slots = NULL; - node->failure_count = 0; - node->data = NULL; - node->migrating = NULL; - node->importing = NULL; - - return REDIS_OK; -} - -static void cluster_node_deinit(cluster_node *node) -{ - copen_slot **oslot; - - if(node == NULL) - { - return; - } - - sdsfree(node->name); - sdsfree(node->addr); - sdsfree(node->host); - node->port = 0; - node->role = REDIS_ROLE_NULL; - node->myself = 0; - - if(node->con != NULL) - { - redisFree(node->con); - } - - if(node->acon != NULL) - { - redisAsyncFree(node->acon); - } - - if(node->slots != NULL) - { - listRelease(node->slots); - } - - if(node->slaves != NULL) - { - listRelease(node->slaves); - } - - if(node->migrating) - { - while(hiarray_n(node->migrating)) - { - oslot = hiarray_pop(node->migrating); - cluster_open_slot_destroy(*oslot); - } - - hiarray_destroy(node->migrating); - node->migrating = NULL; - } - - if(node->importing) - { - while(hiarray_n(node->importing)) - { - oslot = hiarray_pop(node->importing); - cluster_open_slot_destroy(*oslot); - } - - hiarray_destroy(node->importing); - node->importing = NULL; - } -} - -static int cluster_slot_init(cluster_slot *slot, cluster_node *node) -{ - slot->start = 0; - slot->end = 0; - slot->node = node; - - return REDIS_OK; -} - -static cluster_slot *cluster_slot_create(cluster_node *node) -{ - cluster_slot *slot; - - slot = hi_alloc(sizeof(*slot)); - if(slot == NULL){ - return NULL; - } - - cluster_slot_init(slot, node); - - if(node != NULL){ - ASSERT(node->role == REDIS_ROLE_MASTER); - if(node->slots == NULL){ - node->slots = listCreate(); - if(node->slots == NULL) - { - cluster_slot_destroy(slot); - return NULL; - } - - node->slots->free = listClusterSlotDestructor; - } - - listAddNodeTail(node->slots, slot); - } - - return slot; -} - -static int cluster_slot_ref_node(cluster_slot * slot, cluster_node *node) -{ - if(slot == NULL || node == NULL){ - return REDIS_ERR; - } - - - if(node->role != REDIS_ROLE_MASTER){ - return REDIS_ERR; - } - - if(node->slots == NULL){ - node->slots = listCreate(); - if(node->slots == NULL) - { - return REDIS_ERR; - } - - node->slots->free = listClusterSlotDestructor; - } - - listAddNodeTail(node->slots, slot); - slot->node = node; - - return REDIS_OK; -} - -static void cluster_slot_destroy(cluster_slot *slot) -{ - slot->start = 0; - slot->end = 0; - slot->node = NULL; - - hi_free(slot); -} - -static copen_slot *cluster_open_slot_create(uint32_t slot_num, int migrate, - sds remote_name, cluster_node *node) -{ - copen_slot *oslot; - - oslot = hi_alloc(sizeof(*oslot)); - if(oslot == NULL){ - return NULL; - } - - oslot->slot_num = 0; - oslot->migrate = 0; - oslot->node = NULL; - oslot->remote_name = NULL; - - oslot->slot_num = slot_num; - oslot->migrate = migrate; - oslot->node = node; - oslot->remote_name = sdsdup(remote_name); - - return oslot; -} - -static void cluster_open_slot_destroy(copen_slot *oslot) -{ - oslot->slot_num = 0; - oslot->migrate = 0; - oslot->node = NULL; - - if(oslot->remote_name != NULL){ - sdsfree(oslot->remote_name); - oslot->remote_name = NULL; - } - - hi_free(oslot); -} - -/** - * Return a new node with the "cluster slots" command reply. - */ -static cluster_node *node_get_with_slots( - redisClusterContext *cc, redisReply *host_elem, - redisReply *port_elem, uint8_t role) -{ - cluster_node *node = NULL; - - if(host_elem == NULL || port_elem == NULL){ - return NULL; - } - - if(host_elem->type != REDIS_REPLY_STRING || - host_elem->len <= 0){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "node ip is not string."); - goto error; - } - - if(port_elem->type != REDIS_REPLY_INTEGER || - port_elem->integer <= 0){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "node port is not integer."); - goto error; - } - - if(!hi_valid_port((int)port_elem->integer)){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "node port is not valid."); - goto error; - } - - node = hi_alloc(sizeof(cluster_node)); - if(node == NULL){ - __redisClusterSetError(cc, - REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - cluster_node_init(node); - - if(role == REDIS_ROLE_MASTER){ - node->slots = listCreate(); - if(node->slots == NULL){ - hi_free(node); - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "slots for node listCreate error"); - goto error; - } - - node->slots->free = listClusterSlotDestructor; - } - - node->name = NULL; - node->addr = sdsnewlen(host_elem->str, host_elem->len); - node->addr = sdscatfmt(node->addr, ":%i", port_elem->integer); - - node->host = sdsnewlen(host_elem->str, host_elem->len); - node->port = (int)port_elem->integer; - node->role = role; - - return node; - -error: - - if(node != NULL){ - hi_free(node); - } - - return NULL; -} - -/** - * Return a new node with the "cluster nodes" command reply. - */ -static cluster_node *node_get_with_nodes( - redisClusterContext *cc, - sds *node_infos, int info_count, uint8_t role) -{ - sds *ip_port = NULL; - int count_ip_port = 0; - cluster_node *node; - - if(info_count < 8) - { - return NULL; - } - - node = hi_alloc(sizeof(cluster_node)); - if(node == NULL) - { - __redisClusterSetError(cc, - REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - cluster_node_init(node); - - if(role == REDIS_ROLE_MASTER) - { - node->slots = listCreate(); - if(node->slots == NULL) - { - hi_free(node); - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "slots for node listCreate error"); - goto error; - } - - node->slots->free = listClusterSlotDestructor; - } - - node->name = node_infos[0]; - node->addr = node_infos[1]; - - ip_port = sdssplitlen(node_infos[1], sdslen(node_infos[1]), - IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &count_ip_port); - if(ip_port == NULL || count_ip_port != 2) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "split ip port error"); - goto error; - } - node->host = ip_port[0]; - node->port = hi_atoi(ip_port[1], sdslen(ip_port[1])); - node->role = role; - - sdsfree(ip_port[1]); - free(ip_port); - - node_infos[0] = NULL; - node_infos[1] = NULL; - - return node; - -error: - if(ip_port != NULL) - { - sdsfreesplitres(ip_port, count_ip_port); - } - - if(node != NULL) - { - hi_free(node); - } - - return NULL; -} - -static void cluster_nodes_swap_ctx(dict *nodes_f, dict *nodes_t) -{ - dictIterator *di; - dictEntry *de_f, *de_t; - cluster_node *node_f, *node_t; - redisContext *c; - redisAsyncContext *ac; - - if(nodes_f == NULL || nodes_t == NULL){ - return; - } - - di = dictGetIterator(nodes_t); - while((de_t = dictNext(di)) != NULL){ - node_t = dictGetEntryVal(de_t); - if(node_t == NULL){ - continue; - } - - de_f = dictFind(nodes_f, node_t->addr); - if(de_f == NULL){ - continue; - } - - node_f = dictGetEntryVal(de_f); - if(node_f->con != NULL){ - c = node_f->con; - node_f->con = node_t->con; - node_t->con = c; - } - - if(node_f->acon != NULL){ - ac = node_f->acon; - node_f->acon = node_t->acon; - node_t->acon = ac; - - node_t->acon->data = node_t; - if (node_f->acon) - node_f->acon->data = node_f; - } - } - - dictReleaseIterator(di); - -} - -static int -cluster_slot_start_cmp(const void *t1, const void *t2) -{ - const cluster_slot **s1 = t1, **s2 = t2; - - return (*s1)->start > (*s2)->start?1:-1; -} - -static int -cluster_master_slave_mapping_with_name(redisClusterContext *cc, - dict **nodes, cluster_node *node, sds master_name) -{ - int ret; - dictEntry *di; - cluster_node *node_old; - listNode *lnode; - - if(node == NULL || master_name == NULL) - { - return REDIS_ERR; - } - - if(*nodes == NULL) - { - *nodes = dictCreate( - &clusterNodesRefDictType, NULL); - } - - di = dictFind(*nodes, master_name); - if(di == NULL) - { - ret = dictAdd(*nodes, - sdsnewlen(master_name, sdslen(master_name)), node); - if(ret != DICT_OK) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "the address already exists in the nodes"); - return REDIS_ERR; - } - - } - else - { - node_old = dictGetEntryVal(di); - if(node_old == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "dict get value null"); - return REDIS_ERR; - } - - if(node->role == REDIS_ROLE_MASTER && - node_old->role == REDIS_ROLE_MASTER) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "two masters have the same name"); - return REDIS_ERR; - } - else if(node->role == REDIS_ROLE_MASTER - && node_old->role == REDIS_ROLE_SLAVE) - { - if(node->slaves == NULL) - { - node->slaves = listCreate(); - if(node->slaves == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Out of memory"); - return REDIS_ERR; - } - - node->slaves->free = - listClusterNodeDestructor; - } - - if(node_old->slaves != NULL) - { - node_old->slaves->free = NULL; - while(listLength(node_old->slaves) > 0) - { - lnode = listFirst(node_old->slaves); - listAddNodeHead(node->slaves, lnode->value); - listDelNode(node_old->slaves, lnode); - } - listRelease(node_old->slaves); - node_old->slaves = NULL; - } - - listAddNodeHead(node->slaves, node_old); - - dictSetHashVal(*nodes, di, node); - } - else if(node->role == REDIS_ROLE_SLAVE) - { - if(node_old->slaves == NULL) - { - node_old->slaves = listCreate(); - if(node_old->slaves == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Out of memory"); - return REDIS_ERR; - } - - node_old->slaves->free = - listClusterNodeDestructor; - } - - listAddNodeTail(node_old->slaves, node); - } - else - { - NOT_REACHED(); - } - } - - return REDIS_OK; -} - -/** - * Parse the "cluster slots" command reply to nodes dict. - */ -dict * -parse_cluster_slots(redisClusterContext *cc, - redisReply *reply, int flags) -{ - int ret; - cluster_slot *slot = NULL; - dict *nodes = NULL; - dictEntry *den; - redisReply *elem_slots; - redisReply *elem_slots_begin, *elem_slots_end; - redisReply *elem_nodes; - redisReply *elem_ip, *elem_port; - cluster_node *master = NULL, *slave; - sds address; - uint32_t i, idx; - - if(reply == NULL){ - return NULL; - } - - nodes = dictCreate(&clusterNodesDictType, NULL); - if(nodes == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "out of memory"); - goto error; - } - - if(reply->type != REDIS_REPLY_ARRAY || reply->elements <= 0){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "reply is not an array."); - goto error; - } - - for(i = 0; i < reply->elements; i ++){ - elem_slots = reply->element[i]; - if(elem_slots->type != REDIS_REPLY_ARRAY || - elem_slots->elements < 3){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "first sub_reply is not an array."); - goto error; - } - - slot = cluster_slot_create(NULL); - if(slot == NULL){ - __redisClusterSetError(cc, REDIS_ERR_OOM, - "Slot create failed: out of memory."); - goto error; - } - - //one slots region - for(idx = 0; idx < elem_slots->elements; idx ++){ - if(idx == 0){ - elem_slots_begin = elem_slots->element[idx]; - if(elem_slots_begin->type != REDIS_REPLY_INTEGER){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "slot begin is not an integer."); - goto error; - } - slot->start = (int)(elem_slots_begin->integer); - }else if(idx == 1){ - elem_slots_end = elem_slots->element[idx]; - if(elem_slots_end->type != REDIS_REPLY_INTEGER){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "slot end is not an integer."); - goto error; - } - - slot->end = (int)(elem_slots_end->integer); - - if(slot->start > slot->end){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "slot begin is bigger than slot end."); - goto error; - } - }else{ - elem_nodes = elem_slots->element[idx]; - if(elem_nodes->type != REDIS_REPLY_ARRAY || - elem_nodes->elements != 3){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "nodes sub_reply is not an correct array."); - goto error; - } - - elem_ip = elem_nodes->element[0]; - elem_port = elem_nodes->element[1]; - - if(elem_ip == NULL || elem_port == NULL || - elem_ip->type != REDIS_REPLY_STRING || - elem_port->type != REDIS_REPLY_INTEGER){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "master ip or port is not correct."); - goto error; - } - - //this is master. - if(idx == 2){ - address = sdsnewlen(elem_ip->str, elem_ip->len); - address = sdscatfmt(address, ":%i", elem_port->integer); - - den = dictFind(nodes, address); - //master already exits, break to the next slots region. - if(den != NULL){ - sdsfree(address); - - master = dictGetEntryVal(den); - ret = cluster_slot_ref_node(slot, master); - if(ret != REDIS_OK){ - __redisClusterSetError(cc, REDIS_ERR_OOM, - "Slot ref node failed: out of memory."); - goto error; - } - - slot = NULL; - break; - } - - sdsfree(address); - master = node_get_with_slots(cc, elem_ip, - elem_port, REDIS_ROLE_MASTER); - if(master == NULL){ - goto error; - } - - ret = dictAdd(nodes, - sdsnewlen(master->addr, sdslen(master->addr)), master); - if(ret != DICT_OK){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "The address already exists in the nodes"); - cluster_node_deinit(master); - hi_free(master); - goto error; - } - - ret = cluster_slot_ref_node(slot, master); - if(ret != REDIS_OK){ - __redisClusterSetError(cc, REDIS_ERR_OOM, - "Slot ref node failed: out of memory."); - goto error; - } - - slot = NULL; - }else if(flags & HIRCLUSTER_FLAG_ADD_SLAVE){ - slave = node_get_with_slots(cc, elem_ip, - elem_port, REDIS_ROLE_SLAVE); - if(slave == NULL){ - goto error; - } - - if(master->slaves == NULL){ - master->slaves = listCreate(); - if(master->slaves == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Out of memory"); - cluster_node_deinit(slave); - goto error; - } - - master->slaves->free = - listClusterNodeDestructor; - } - - listAddNodeTail(master->slaves, slave); - } - } - } - } - - return nodes; - -error: - - if(nodes != NULL){ - dictRelease(nodes); - } - - if(slot != NULL){ - cluster_slot_destroy(slot); - } - - return NULL; -} - -/** - * Parse the "cluster nodes" command reply to nodes dict. - */ -dict * -parse_cluster_nodes(redisClusterContext *cc, - char *str, int str_len, int flags) -{ - int ret; - dict *nodes = NULL; - dict *nodes_name = NULL; - cluster_node *master, *slave; - cluster_slot *slot; - char *pos, *start, *end, *line_start, *line_end; - char *role; - int role_len; - uint8_t myself = 0; - int slot_start, slot_end; - sds *part = NULL, *slot_start_end = NULL; - int count_part = 0, count_slot_start_end = 0; - int k; - int len; - - nodes = dictCreate(&clusterNodesDictType, NULL); - if(nodes == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "out of memory"); - goto error; - } - - start = str; - end = start + str_len; - - line_start = start; - - for(pos = start; pos < end; pos ++){ - if(*pos == '\n'){ - line_end = pos - 1; - len = line_end - line_start; - - part = sdssplitlen(line_start, len + 1, " ", 1, &count_part); - - if(part == NULL || count_part < 8){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "split cluster nodes error"); - goto error; - } - - //the address string is ":0", skip this node. - if(sdslen(part[1]) == 2 && strcmp(part[1], ":0") == 0){ - sdsfreesplitres(part, count_part); - count_part = 0; - part = NULL; - - start = pos + 1; - line_start = start; - pos = start; - - continue; - } - - if(sdslen(part[2]) >= 7 && memcmp(part[2], "myself,", 7) == 0){ - role_len = sdslen(part[2]) - 7; - role = part[2] + 7; - myself = 1; - }else{ - role_len = sdslen(part[2]); - role = part[2]; - } - - //add master node - if(role_len >= 6 && memcmp(role, "master", 6) == 0){ - if(count_part < 8){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Master node parts number error: less than 8."); - goto error; - } - - master = node_get_with_nodes(cc, - part, count_part, REDIS_ROLE_MASTER); - if(master == NULL){ - goto error; - } - - ret = dictAdd(nodes, - sdsnewlen(master->addr, sdslen(master->addr)), master); - if(ret != DICT_OK){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "The address already exists in the nodes"); - cluster_node_deinit(master); - hi_free(master); - goto error; - } - - if(flags & HIRCLUSTER_FLAG_ADD_SLAVE){ - ret = cluster_master_slave_mapping_with_name(cc, - &nodes_name, master, master->name); - if(ret != REDIS_OK){ - cluster_node_deinit(master); - hi_free(master); - goto error; - } - } - - if(myself) master->myself = 1; - - for(k = 8; k < count_part; k ++){ - slot_start_end = sdssplitlen(part[k], - sdslen(part[k]), "-", 1, &count_slot_start_end); - - if(slot_start_end == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "split slot start end error(NULL)"); - goto error; - }else if(count_slot_start_end == 1){ - slot_start = - hi_atoi(slot_start_end[0], sdslen(slot_start_end[0])); - slot_end = slot_start; - }else if(count_slot_start_end == 2){ - slot_start = - hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));; - slot_end = - hi_atoi(slot_start_end[1], sdslen(slot_start_end[1]));; - }else{ - //add open slot for master - if(flags & HIRCLUSTER_FLAG_ADD_OPENSLOT && - count_slot_start_end == 3 && - sdslen(slot_start_end[0]) > 1 && - sdslen(slot_start_end[1]) == 1 && - sdslen(slot_start_end[2]) > 1 && - slot_start_end[0][0] == '[' && - slot_start_end[2][sdslen(slot_start_end[2])-1] == ']'){ - - copen_slot *oslot, **oslot_elem; - - sdsrange(slot_start_end[0], 1, -1); - sdsrange(slot_start_end[2], 0, -2); - - if(slot_start_end[1][0] == '>'){ - oslot = cluster_open_slot_create( - hi_atoi(slot_start_end[0], - sdslen(slot_start_end[0])), - 1, slot_start_end[2], master); - if(oslot == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "create open slot error"); - goto error; - } - - if(master->migrating == NULL){ - master->migrating = hiarray_create(1, sizeof(oslot)); - if(master->migrating == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "create migrating array error"); - cluster_open_slot_destroy(oslot); - goto error; - } - } - - oslot_elem = hiarray_push(master->migrating); - if(oslot_elem == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Push migrating array error: out of memory"); - cluster_open_slot_destroy(oslot); - goto error; - } - - *oslot_elem = oslot; - }else if(slot_start_end[1][0] == '<'){ - oslot = cluster_open_slot_create(hi_atoi(slot_start_end[0], - sdslen(slot_start_end[0])), 0, slot_start_end[2], - master); - if(oslot == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "create open slot error"); - goto error; - } - - if(master->importing == NULL){ - master->importing = hiarray_create(1, sizeof(oslot)); - if(master->importing == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "create migrating array error"); - cluster_open_slot_destroy(oslot); - goto error; - } - } - - oslot_elem = hiarray_push(master->importing); - if(oslot_elem == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "push migrating array error: out of memory"); - cluster_open_slot_destroy(oslot); - goto error; - } - - *oslot_elem = oslot; - } - } - - slot_start = -1; - slot_end = -1; - } - - sdsfreesplitres(slot_start_end, count_slot_start_end); - count_slot_start_end = 0; - slot_start_end = NULL; - - if(slot_start < 0 || slot_end < 0 || - slot_start > slot_end || slot_end >= REDIS_CLUSTER_SLOTS){ - continue; - } - - slot = cluster_slot_create(master); - if(slot == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Out of memory"); - goto error; - } - - slot->start = (uint32_t)slot_start; - slot->end = (uint32_t)slot_end; - } - - } - //add slave node - else if((flags & HIRCLUSTER_FLAG_ADD_SLAVE) && - (role_len >= 5 && memcmp(role, "slave", 5) == 0)){ - slave = node_get_with_nodes(cc, part, - count_part, REDIS_ROLE_SLAVE); - if(slave == NULL){ - goto error; - } - - ret = cluster_master_slave_mapping_with_name(cc, - &nodes_name, slave, part[3]); - if(ret != REDIS_OK){ - cluster_node_deinit(slave); - hi_free(slave); - goto error; - } - - if(myself) slave->myself = 1; - } - - if(myself == 1){ - myself = 0; - } - - sdsfreesplitres(part, count_part); - count_part = 0; - part = NULL; - - start = pos + 1; - line_start = start; - pos = start; - } - } - - if(nodes_name != NULL){ - dictRelease(nodes_name); - } - - return nodes; - -error: - - if(part != NULL){ - sdsfreesplitres(part, count_part); - count_part = 0; - part = NULL; - } - - if(slot_start_end != NULL){ - sdsfreesplitres(slot_start_end, count_slot_start_end); - count_slot_start_end = 0; - slot_start_end = NULL; - } - - if(nodes != NULL){ - dictRelease(nodes); - } - - if(nodes_name != NULL){ - dictRelease(nodes_name); - } - - return NULL; -} - -/** - * Update route with the "cluster nodes" or "cluster slots" command reply. - */ -static int -cluster_update_route_by_addr(redisClusterContext *cc, - const char *ip, int port) -{ - redisContext *c = NULL; - redisReply *reply = NULL; - dict *nodes = NULL; - struct hiarray *slots = NULL; - cluster_node *master; - cluster_slot *slot, **slot_elem; - dictIterator *dit = NULL; - dictEntry *den; - listIter *lit = NULL; - listNode *lnode; - cluster_node *table[REDIS_CLUSTER_SLOTS]; - uint32_t j, k; - - if(cc == NULL){ - return REDIS_ERR; - } - - if(ip == NULL || port <= 0){ - __redisClusterSetError(cc, - REDIS_ERR_OTHER,"Ip or port error!"); - goto error; - } - - if(cc->timeout){ - c = redisConnectWithTimeout(ip, port, *cc->timeout); - }else{ - c = redisConnect(ip, port); - } - - if (c == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Init redis context error(return NULL)"); - goto error; - }else if(c->err){ - __redisClusterSetError(cc,c->err,c->errstr); - goto error; - } - - if(cc->flags & HIRCLUSTER_FLAG_ROUTE_USE_SLOTS){ - reply = redisCommand(c, REDIS_COMMAND_CLUSTER_SLOTS); - if(reply == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Command(cluster slots) reply error(NULL)."); - goto error; - }else if(reply->type != REDIS_REPLY_ARRAY){ - if(reply->type == REDIS_REPLY_ERROR){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - reply->str); - }else{ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Command(cluster slots) reply error: type is not array."); - } - - goto error; - } - - nodes = parse_cluster_slots(cc, reply, cc->flags); - }else{ - reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES); - if(reply == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Command(cluster nodes) reply error(NULL)."); - goto error; - }else if(reply->type != REDIS_REPLY_STRING){ - if(reply->type == REDIS_REPLY_ERROR){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - reply->str); - }else{ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Command(cluster nodes) reply error: type is not string."); - } - - goto error; - } - - nodes = parse_cluster_nodes(cc, reply->str, reply->len, cc->flags); - } - - if(nodes == NULL){ - goto error; - } - - memset(table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); - - slots = hiarray_create(dictSize(nodes), sizeof(cluster_slot*)); - if(slots == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Slots array create failed: out of memory"); - goto error; - } - - dit = dictGetIterator(nodes); - if(dit == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Dict get iterator failed: out of memory"); - goto error; - } - - while((den = dictNext(dit))){ - master = dictGetEntryVal(den); - if(master->role != REDIS_ROLE_MASTER){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Node role must be master"); - goto error; - } - - if(master->slots == NULL){ - continue; - } - - lit = listGetIterator(master->slots, AL_START_HEAD); - if(lit == NULL){ - __redisClusterSetError(cc, REDIS_ERR_OOM, - "List get iterator failed: out of memory"); - goto error; - } - - while((lnode = listNext(lit))){ - slot = listNodeValue(lnode); - if(slot->start > slot->end || - slot->end >= REDIS_CLUSTER_SLOTS){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Slot region for node is error"); - goto error; - } - - slot_elem = hiarray_push(slots); - *slot_elem = slot; - } - - listReleaseIterator(lit); - } - - dictReleaseIterator(dit); - - hiarray_sort(slots, cluster_slot_start_cmp); - for(j = 0; j < hiarray_n(slots); j ++){ - slot_elem = hiarray_get(slots, j); - - for(k = (*slot_elem)->start; k <= (*slot_elem)->end; k ++){ - if(table[k] != NULL){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Diffent node hold a same slot"); - goto error; - } - - table[k] = (*slot_elem)->node; - } - } - - cluster_nodes_swap_ctx(cc->nodes, nodes); - if(cc->nodes != NULL){ - dictRelease(cc->nodes); - cc->nodes = NULL; - } - cc->nodes = nodes; - - if(cc->slots != NULL) - { - cc->slots->nelem = 0; - hiarray_destroy(cc->slots); - cc->slots = NULL; - } - cc->slots = slots; - - memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); - cc->route_version ++; - - freeReplyObject(reply); - - if(c != NULL){ - redisFree(c); - } - - return REDIS_OK; - -error: - - if(dit != NULL){ - dictReleaseIterator(dit); - } - - if(lit != NULL){ - listReleaseIterator(lit); - } - - if(slots != NULL) - { - if(slots == cc->slots) - { - cc->slots = NULL; - } - - slots->nelem = 0; - hiarray_destroy(slots); - } - - if(nodes != NULL){ - if(nodes == cc->nodes){ - cc->nodes = NULL; - } - - dictRelease(nodes); - } - - if(reply != NULL){ - freeReplyObject(reply); - reply = NULL; - } - - if(c != NULL){ - redisFree(c); - } - - return REDIS_ERR; -} - - -/** - * Update route with the "cluster nodes" command reply. - */ -static int -cluster_update_route_with_nodes_old(redisClusterContext *cc, - const char *ip, int port) -{ - int ret; - redisContext *c = NULL; - redisReply *reply = NULL; - struct hiarray *slots = NULL; - dict *nodes = NULL; - dict *nodes_name = NULL; - cluster_node *master, *slave; - cluster_slot **slot; - char *pos, *start, *end, *line_start, *line_end; - char *role; - int role_len; - uint8_t myself = 0; - int slot_start, slot_end; - sds *part = NULL, *slot_start_end = NULL; - int count_part = 0, count_slot_start_end = 0; - int j, k; - int len; - cluster_node *table[REDIS_CLUSTER_SLOTS] = {NULL}; - - if(cc == NULL) - { - return REDIS_ERR; - } - - if(ip == NULL || port <= 0) - { - __redisClusterSetError(cc, - REDIS_ERR_OTHER,"ip or port error!"); - goto error; - } - - if(cc->timeout) - { - c = redisConnectWithTimeout(ip, port, *cc->timeout); - } - else - { - c = redisConnect(ip, port); - } - - if (c == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "init redis context error(return NULL)"); - goto error; - } - else if(c->err) - { - __redisClusterSetError(cc,c->err,c->errstr); - goto error; - } - - reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES); - - if(reply == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "command(cluster nodes) reply error(NULL)"); - goto error; - } - else if(reply->type != REDIS_REPLY_STRING) - { - if(reply->type == REDIS_REPLY_ERROR) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - reply->str); - } - else - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "command(cluster nodes) reply error(type is not string)"); - } - - goto error; - } - - nodes = dictCreate(&clusterNodesDictType, NULL); - - slots = hiarray_create(10, sizeof(cluster_slot*)); - if(slots == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "array create error"); - goto error; - } - - start = reply->str; - end = start + reply->len; - - line_start = start; - - for(pos = start; pos < end; pos ++) - { - if(*pos == '\n') - { - line_end = pos - 1; - len = line_end - line_start; - - part = sdssplitlen(line_start, len + 1, " ", 1, &count_part); - - if(part == NULL || count_part < 8) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "split cluster nodes error"); - goto error; - } - - //the address string is ":0", skip this node. - if(sdslen(part[1]) == 2 && strcmp(part[1], ":0") == 0) - { - sdsfreesplitres(part, count_part); - count_part = 0; - part = NULL; - - start = pos + 1; - line_start = start; - pos = start; - - continue; - } - - if(sdslen(part[2]) >= 7 && memcmp(part[2], "myself,", 7) == 0) - { - role_len = sdslen(part[2]) - 7; - role = part[2] + 7; - myself = 1; - } - else - { - role_len = sdslen(part[2]); - role = part[2]; - } - - //add master node - if(role_len >= 6 && memcmp(role, "master", 6) == 0) - { - if(count_part < 8) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "master node part number error"); - goto error; - } - - master = node_get_with_nodes(cc, - part, count_part, REDIS_ROLE_MASTER); - if(master == NULL) - { - goto error; - } - - ret = dictAdd(nodes, - sdsnewlen(master->addr, sdslen(master->addr)), master); - if(ret != DICT_OK) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "the address already exists in the nodes"); - cluster_node_deinit(master); - hi_free(master); - goto error; - } - - if(cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE) - { - ret = cluster_master_slave_mapping_with_name(cc, - &nodes_name, master, master->name); - if(ret != REDIS_OK) - { - cluster_node_deinit(master); - hi_free(master); - goto error; - } - } - - if(myself == 1) - { - master->con = c; - c = NULL; - } - - for(k = 8; k < count_part; k ++) - { - slot_start_end = sdssplitlen(part[k], - sdslen(part[k]), "-", 1, &count_slot_start_end); - - if(slot_start_end == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "split slot start end error(NULL)"); - goto error; - } - else if(count_slot_start_end == 1) - { - slot_start = - hi_atoi(slot_start_end[0], sdslen(slot_start_end[0])); - slot_end = slot_start; - } - else if(count_slot_start_end == 2) - { - slot_start = - hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));; - slot_end = - hi_atoi(slot_start_end[1], sdslen(slot_start_end[1]));; - } - else - { - slot_start = -1; - slot_end = -1; - } - - sdsfreesplitres(slot_start_end, count_slot_start_end); - count_slot_start_end = 0; - slot_start_end = NULL; - - if(slot_start < 0 || slot_end < 0 || - slot_start > slot_end || slot_end >= REDIS_CLUSTER_SLOTS) - { - continue; - } - - for(j = slot_start; j <= slot_end; j ++) - { - if(table[j] != NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "diffent node hold a same slot"); - goto error; - } - table[j] = master; - } - - slot = hiarray_push(slots); - if(slot == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "slot push in array error"); - goto error; - } - - *slot = cluster_slot_create(master); - if(*slot == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Out of memory"); - goto error; - } - - (*slot)->start = (uint32_t)slot_start; - (*slot)->end = (uint32_t)slot_end; - } - - } - //add slave node - else if((cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE) && - (role_len >= 5 && memcmp(role, "slave", 5) == 0)) - { - slave = node_get_with_nodes(cc, part, - count_part, REDIS_ROLE_SLAVE); - if(slave == NULL) - { - goto error; - } - - ret = cluster_master_slave_mapping_with_name(cc, - &nodes_name, slave, part[3]); - if(ret != REDIS_OK) - { - cluster_node_deinit(slave); - hi_free(slave); - goto error; - } - - if(myself == 1) - { - slave->con = c; - c = NULL; - } - } - - if(myself == 1) - { - myself = 0; - } - - sdsfreesplitres(part, count_part); - count_part = 0; - part = NULL; - - start = pos + 1; - line_start = start; - pos = start; - } - } - - if(cc->slots != NULL) - { - cc->slots->nelem = 0; - hiarray_destroy(cc->slots); - cc->slots = NULL; - } - cc->slots = slots; - - cluster_nodes_swap_ctx(cc->nodes, nodes); - - if(cc->nodes != NULL) - { - dictRelease(cc->nodes); - cc->nodes = NULL; - } - cc->nodes = nodes; - - hiarray_sort(cc->slots, cluster_slot_start_cmp); - - memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); - cc->route_version ++; - - freeReplyObject(reply); - - if(c != NULL) - { - redisFree(c); - } - - if(nodes_name != NULL) - { - dictRelease(nodes_name); - } - - return REDIS_OK; - -error: - - if(part != NULL) - { - sdsfreesplitres(part, count_part); - count_part = 0; - part = NULL; - } - - if(slot_start_end != NULL) - { - sdsfreesplitres(slot_start_end, count_slot_start_end); - count_slot_start_end = 0; - slot_start_end = NULL; - } - - if(slots != NULL) - { - if(slots == cc->slots) - { - cc->slots = NULL; - } - - slots->nelem = 0; - hiarray_destroy(slots); - } - - if(nodes != NULL) - { - if(nodes == cc->nodes) - { - cc->nodes = NULL; - } - - dictRelease(nodes); - } - - if(nodes_name != NULL) - { - dictRelease(nodes_name); - } - - if(reply != NULL) - { - freeReplyObject(reply); - reply = NULL; - } - - if(c != NULL) - { - redisFree(c); - } - - return REDIS_ERR; -} - -int -cluster_update_route(redisClusterContext *cc) -{ - int ret; - int flag_err_not_set = 1; - cluster_node *node; - dictIterator *it; - dictEntry *de; - - if(cc == NULL) - { - return REDIS_ERR; - } - - if(cc->ip != NULL && cc->port > 0) - { - ret = cluster_update_route_by_addr(cc, cc->ip, cc->port); - if(ret == REDIS_OK) - { - return REDIS_OK; - } - - flag_err_not_set = 0; - } - - if(cc->nodes == NULL) - { - if(flag_err_not_set) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "no server address"); - } - - return REDIS_ERR; - } - - it = dictGetIterator(cc->nodes); - while ((de = dictNext(it)) != NULL) - { - node = dictGetEntryVal(de); - if(node == NULL || node->host == NULL || node->port < 0) - { - continue; - } - - ret = cluster_update_route_by_addr(cc, node->host, node->port); - if(ret == REDIS_OK) - { - if(cc->err) - { - cc->err = 0; - memset(cc->errstr, '\0', strlen(cc->errstr)); - } - - dictReleaseIterator(it); - return REDIS_OK; - } - - flag_err_not_set = 0; - } - - dictReleaseIterator(it); - - if(flag_err_not_set) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "no valid server address"); - } - - return REDIS_ERR; -} - -static void print_cluster_node_list(redisClusterContext *cc) -{ - dictIterator *di = NULL; - dictEntry *de; - listIter *it; - listNode *ln; - cluster_node *master, *slave; - hilist *slaves; - - if(cc == NULL) - { - return; - } - - di = dictGetIterator(cc->nodes); - - printf("name\taddress\trole\tslaves\n"); - - while((de = dictNext(di)) != NULL) { - master = dictGetEntryVal(de); - - printf("%s\t%s\t%d\t%s\n",master->name, master->addr, - master->role, master->slaves?"hava":"null"); - - slaves = master->slaves; - if(slaves == NULL) - { - continue; - } - - it = listGetIterator(slaves, AL_START_HEAD); - while((ln = listNext(it)) != NULL) - { - slave = listNodeValue(ln); - printf("%s\t%s\t%d\t%s\n",slave->name, slave->addr, - slave->role, slave->slaves?"hava":"null"); - } - - listReleaseIterator(it); - - printf("\n"); - } -} - - -int test_cluster_update_route(redisClusterContext *cc) -{ - int ret; - - ret = cluster_update_route(cc); - - //print_cluster_node_list(cc); - - return ret; -} - -static redisClusterContext *redisClusterContextInit(void) { - redisClusterContext *cc; - - cc = calloc(1,sizeof(redisClusterContext)); - if (cc == NULL) - return NULL; - - cc->err = 0; - cc->errstr[0] = '\0'; - cc->ip = NULL; - cc->port = 0; - cc->flags = 0; - cc->timeout = NULL; - cc->nodes = NULL; - cc->slots = NULL; - cc->max_redirect_count = CLUSTER_DEFAULT_MAX_REDIRECT_COUNT; - cc->retry_count = 0; - cc->requests = NULL; - cc->need_update_route = 0; - cc->update_route_time = 0LL; - - cc->route_version = 0LL; - - memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); - - return cc; -} - -void redisClusterFree(redisClusterContext *cc) { - - if (cc == NULL) - return; - - if(cc->ip) - { - sdsfree(cc->ip); - cc->ip = NULL; - } - - if (cc->timeout) - { - free(cc->timeout); - } - - memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); - - if(cc->slots != NULL) - { - cc->slots->nelem = 0; - hiarray_destroy(cc->slots); - cc->slots = NULL; - } - - if(cc->nodes != NULL) - { - dictRelease(cc->nodes); - } - - if(cc->requests != NULL) - { - listRelease(cc->requests); - } - - free(cc); -} - -static int redisClusterAddNode(redisClusterContext *cc, const char *addr) -{ - dictEntry *node_entry; - cluster_node *node; - sds *ip_port = NULL; - int ip_port_count = 0; - sds ip; - int port; - - if(cc == NULL) - { - return REDIS_ERR; - } - - if(cc->nodes == NULL) - { - cc->nodes = dictCreate(&clusterNodesDictType, NULL); - if(cc->nodes == NULL) - { - return REDIS_ERR; - } - } - - node_entry = dictFind(cc->nodes, addr); - if(node_entry == NULL) - { - ip_port = sdssplitlen(addr, strlen(addr), - IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &ip_port_count); - if(ip_port == NULL || ip_port_count != 2 || - sdslen(ip_port[0]) <= 0 || sdslen(ip_port[1]) <= 0) - { - if(ip_port != NULL) - { - sdsfreesplitres(ip_port, ip_port_count); - } - __redisClusterSetError(cc,REDIS_ERR_OTHER,"server address is error(correct is like: 127.0.0.1:1234)"); - return REDIS_ERR; - } - - ip = ip_port[0]; - port = hi_atoi(ip_port[1], sdslen(ip_port[1])); - - if(port <= 0) - { - sdsfreesplitres(ip_port, ip_port_count); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"server port is error"); - return REDIS_ERR; - } - - sdsfree(ip_port[1]); - free(ip_port); - ip_port = NULL; - - node = hi_alloc(sizeof(cluster_node)); - if(node == NULL) - { - sdsfree(ip); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"alloc cluster node error"); - return REDIS_ERR; - } - - cluster_node_init(node); - - node->addr = sdsnew(addr); - if(node->addr == NULL) - { - sdsfree(ip); - hi_free(node); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"new node address error"); - return REDIS_ERR; - } - - node->host = ip; - node->port = port; - - dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); - } - - return REDIS_OK; -} - - -/* Connect to a Redis cluster. On error the field error in the returned - * context will be set to the return value of the error function. - * When no set of reply functions is given, the default set will be used. */ -static redisClusterContext *_redisClusterConnect(redisClusterContext *cc, const char *addrs) { - - int ret; - sds *address = NULL; - int address_count = 0; - int i; - - if(cc == NULL) - { - return NULL; - } - - - address = sdssplitlen(addrs, strlen(addrs), CLUSTER_ADDRESS_SEPARATOR, - strlen(CLUSTER_ADDRESS_SEPARATOR), &address_count); - if(address == NULL || address_count <= 0) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"servers address is error(correct is like: 127.0.0.1:1234,127.0.0.2:5678)"); - return cc; - } - - for(i = 0; i < address_count; i ++) - { - ret = redisClusterAddNode(cc, address[i]); - if(ret != REDIS_OK) - { - sdsfreesplitres(address, address_count); - return cc; - } - } - - sdsfreesplitres(address, address_count); - - cluster_update_route(cc); - - return cc; -} - -redisClusterContext *redisClusterConnect(const char *addrs, int flags) -{ - redisClusterContext *cc; - - cc = redisClusterContextInit(); - - if(cc == NULL) - { - return NULL; - } - - cc->flags |= REDIS_BLOCK; - if(flags) - { - cc->flags |= flags; - } - - return _redisClusterConnect(cc, addrs); -} - -redisClusterContext *redisClusterConnectWithTimeout( - const char *addrs, const struct timeval tv, int flags) -{ - redisClusterContext *cc; - - cc = redisClusterContextInit(); - - if(cc == NULL) - { - return NULL; - } - - cc->flags |= REDIS_BLOCK; - if(flags) - { - cc->flags |= flags; - } - - if (cc->timeout == NULL) - { - cc->timeout = malloc(sizeof(struct timeval)); - } - - memcpy(cc->timeout, &tv, sizeof(struct timeval)); - - return _redisClusterConnect(cc, addrs); -} - -redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags) { - - redisClusterContext *cc; - - cc = redisClusterContextInit(); - - if(cc == NULL) - { - return NULL; - } - - cc->flags &= ~REDIS_BLOCK; - if(flags) - { - cc->flags |= flags; - } - - return _redisClusterConnect(cc, addrs); -} - -redisContext *ctx_get_by_node(cluster_node *node, - const struct timeval *timeout, int flags) -{ - redisContext *c = NULL; - if(node == NULL) - { - return NULL; - } - - c = node->con; - if(c != NULL) - { - if(c->err) - { - redisReconnect(c); - } - - return c; - } - - if(node->host == NULL || node->port <= 0) - { - return NULL; - } - - if(flags & REDIS_BLOCK) - { - if(timeout) - { - c = redisConnectWithTimeout(node->host, node->port, *timeout); - } - else - { - c = redisConnect(node->host, node->port); - } - } - else - { - c = redisConnectNonBlock(node->host, node->port); - } - - node->con = c; - - return c; -} - -static cluster_node *node_get_by_slot(redisClusterContext *cc, uint32_t slot_num) -{ - struct hiarray *slots; - uint32_t slot_count; - cluster_slot **slot; - uint32_t middle, start, end; - uint8_t stop = 0; - - if(cc == NULL) - { - return NULL; - } - - if(slot_num >= REDIS_CLUSTER_SLOTS) - { - return NULL; - } - - slots = cc->slots; - if(slots == NULL) - { - return NULL; - } - slot_count = hiarray_n(slots); - - start = 0; - end = slot_count - 1; - middle = 0; - - do{ - if(start >= end) - { - stop = 1; - middle = end; - } - else - { - middle = start + (end - start)/2; - } - - ASSERT(middle < slot_count); - - slot = hiarray_get(slots, middle); - if((*slot)->start > slot_num) - { - end = middle - 1; - } - else if((*slot)->end < slot_num) - { - start = middle + 1; - } - else - { - return (*slot)->node; - } - - - }while(!stop); - - printf("slot_num : %d\n", slot_num); - printf("slot_count : %d\n", slot_count); - printf("start : %d\n", start); - printf("end : %d\n", end); - printf("middle : %d\n", middle); - - return NULL; -} - - -static cluster_node *node_get_by_table(redisClusterContext *cc, uint32_t slot_num) -{ - if(cc == NULL) - { - return NULL; - } - - if(slot_num >= REDIS_CLUSTER_SLOTS) - { - return NULL; - } - - return cc->table[slot_num]; - -} - -static cluster_node *node_get_witch_connected(redisClusterContext *cc) -{ - dictIterator *di; - dictEntry *de; - struct cluster_node *node; - redisContext *c = NULL; - redisReply *reply = NULL; - - if(cc == NULL || cc->nodes == NULL) - { - return NULL; - } - - di = dictGetIterator(cc->nodes); - while((de = dictNext(di)) != NULL) - { - node = dictGetEntryVal(de); - if(node == NULL) - { - continue; - } - - c = ctx_get_by_node(node, cc->timeout, REDIS_BLOCK); - if(c == NULL || c->err) - { - continue; - } - - reply = redisCommand(c, REDIS_COMMAND_PING); - if(reply != NULL && reply->type == REDIS_REPLY_STATUS && - reply->str != NULL && strcmp(reply->str, "PONG") == 0) - { - freeReplyObject(reply); - reply = NULL; - - dictReleaseIterator(di); - - return node; - } - else if(reply != NULL) - { - freeReplyObject(reply); - reply = NULL; - } - } - - dictReleaseIterator(di); - - return NULL; -} - -static int slot_get_by_command(redisClusterContext *cc, char *cmd, int len) -{ - struct cmd *command = NULL; - struct keypos *kp; - int key_count; - uint32_t i; - int slot_num = -1; - - if(cc == NULL || cmd == NULL || len <= 0) - { - goto done; - } - - command = command_get(); - if(command == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto done; - } - - command->cmd = cmd; - command->clen = len; - redis_parse_cmd(command); - if(command->result != CMD_PARSE_OK) - { - __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "parse command error"); - goto done; - } - - key_count = hiarray_n(command->keys); - - if(key_count <= 0) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "no keys in command(must have keys for redis cluster mode)"); - goto done; - } - else if(key_count == 1) - { - kp = hiarray_get(command->keys, 0); - slot_num = keyHashSlot(kp->start, kp->end - kp->start); - - goto done; - } - - for(i = 0; i < hiarray_n(command->keys); i ++) - { - kp = hiarray_get(command->keys, i); - - slot_num = keyHashSlot(kp->start, kp->end - kp->start); - } - -done: - - if(command != NULL) - { - command->cmd = NULL; - command_destroy(command); - } - - return slot_num; -} - -/* Get the cluster config from one node. - * Return value: config_value string must free by usr. - */ -static char * cluster_config_get(redisClusterContext *cc, - const char *config_name, int *config_value_len) -{ - redisContext *c; - cluster_node *node; - redisReply *reply = NULL, *sub_reply; - char *config_value = NULL; - - if(cc == NULL || config_name == NULL - || config_value_len == NULL) - { - return NULL; - } - - node = node_get_witch_connected(cc); - if(node == NULL) - { - __redisClusterSetError(cc, - REDIS_ERR_OTHER, "no reachable node in cluster"); - goto error; - } - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - - reply = redisCommand(c, "config get %s", config_name); - if(reply == NULL) - { - __redisClusterSetError(cc, - REDIS_ERR_OTHER, "reply for config get is null"); - goto error; - } - - if(reply->type != REDIS_REPLY_ARRAY) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "reply for config get type is not array"); - goto error; - } - - if(reply->elements != 2) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "reply for config get elements number is not 2"); - goto error; - } - - sub_reply = reply->element[0]; - if(sub_reply == NULL || sub_reply->type != REDIS_REPLY_STRING) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "reply for config get config name is not string"); - goto error; - } - - if(strcmp(sub_reply->str, config_name)) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "reply for config get config name is not we want"); - goto error; - } - - sub_reply = reply->element[1]; - if(sub_reply == NULL || sub_reply->type != REDIS_REPLY_STRING) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "reply for config get config value type is not string"); - goto error; - } - - config_value = sub_reply->str; - *config_value_len = sub_reply->len; - sub_reply->str= NULL; - - if(reply != NULL) - { - freeReplyObject(reply); - } - - return config_value; - -error: - - if(reply != NULL) - { - freeReplyObject(reply); - } - - return NULL; -} - -/* Helper function for the redisClusterAppendCommand* family of functions. - * - * Write a formatted command to the output buffer. When this family - * is used, you need to call redisGetReply yourself to retrieve - * the reply (or replies in pub/sub). - */ -static int __redisClusterAppendCommand(redisClusterContext *cc, - struct cmd *command) { - - cluster_node *node; - redisContext *c = NULL; - - if(cc == NULL || command == NULL) - { - return REDIS_ERR; - } - - node = node_get_by_table(cc, (uint32_t)command->slot_num); - if(node == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by slot error"); - return REDIS_ERR; - } - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null"); - return REDIS_ERR; - } - else if(c->err) - { - __redisClusterSetError(cc, c->err, c->errstr); - return REDIS_ERR; - } - - if (__redisAppendCommand(c, command->cmd, command->clen) != REDIS_OK) - { - __redisClusterSetError(cc, c->err, c->errstr); - return REDIS_ERR; - } - - return REDIS_OK; -} - -/* Helper function for the redisClusterGetReply* family of functions. - */ -static int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply) -{ - cluster_node *node; - redisContext *c; - - if(cc == NULL || slot_num < 0 || reply == NULL) - { - return REDIS_ERR; - } - - node = node_get_by_table(cc, (uint32_t)slot_num); - if(node == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table is null"); - return REDIS_ERR; - } - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - else if(c->err) - { - if(cc->need_update_route == 0) - { - cc->retry_count ++; - if(cc->retry_count > cc->max_redirect_count) - { - cc->need_update_route = 1; - cc->retry_count = 0; - } - } - __redisClusterSetError(cc, c->err, c->errstr); - return REDIS_ERR; - } - - if(redisGetReply(c, reply) != REDIS_OK) - { - __redisClusterSetError(cc, c->err, c->errstr); - return REDIS_ERR; - } - - if(cluster_reply_error_type(*reply) == CLUSTER_ERR_MOVED) - { - cc->need_update_route = 1; - } - - return REDIS_OK; -} - -static cluster_node *node_get_by_ask_error_reply( - redisClusterContext *cc, redisReply *reply) -{ - sds *part = NULL, *ip_port = NULL; - int part_len = 0, ip_port_len; - dictEntry *de; - cluster_node *node = NULL; - - if(cc == NULL || reply == NULL) - { - return NULL; - } - - if(cluster_reply_error_type(reply) != CLUSTER_ERR_ASK) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "reply is not ask error!"); - return NULL; - } - - part = sdssplitlen(reply->str, reply->len, " ", 1, &part_len); - - if(part != NULL && part_len == 3) - { - ip_port = sdssplitlen(part[2], sdslen(part[2]), - ":", 1, &ip_port_len); - - if(ip_port != NULL && ip_port_len == 2) - { - de = dictFind(cc->nodes, part[2]); - if(de == NULL) - { - node = hi_alloc(sizeof(cluster_node)); - if(node == NULL) - { - __redisClusterSetError(cc, - REDIS_ERR_OOM, "Out of memory"); - - goto done; - } - - cluster_node_init(node); - node->addr = part[1]; - node->host = ip_port[0]; - node->port = hi_atoi(ip_port[1], sdslen(ip_port[1])); - node->role = REDIS_ROLE_MASTER; - - dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); - - part = NULL; - ip_port = NULL; - } - else - { - node = de->val; - - goto done; - } - } - else - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "ask error reply address part parse error!"); - - goto done; - } - - } - else - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "ask error reply parse error!"); - - goto done; - } - -done: - - if(part != NULL) - { - sdsfreesplitres(part, part_len); - part = NULL; - } - - if(ip_port != NULL) - { - sdsfreesplitres(ip_port, ip_port_len); - ip_port = NULL; - } - - return node; -} - -static void *redis_cluster_command_execute(redisClusterContext *cc, - struct cmd *command) -{ - int ret; - void *reply = NULL; - cluster_node *node; - redisContext *c = NULL; - int error_type; - -retry: - - node = node_get_by_table(cc, (uint32_t)command->slot_num); - if(node == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table error"); - return NULL; - } - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null"); - return NULL; - } - else if(c->err) - { - node = node_get_witch_connected(cc); - if(node == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "no reachable node in cluster"); - return NULL; - } - - cc->retry_count ++; - if(cc->retry_count > cc->max_redirect_count) - { - __redisClusterSetError(cc, REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, - "too many cluster redirect"); - return NULL; - } - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error"); - return NULL; - } - else if(c->err) - { - __redisClusterSetError(cc, c->err, c->errstr); - return NULL; - } - } - -ask_retry: - - if (__redisAppendCommand(c,command->cmd, command->clen) != REDIS_OK) - { - __redisClusterSetError(cc, c->err, c->errstr); - return NULL; - } - - reply = __redisBlockForReply(c); - if(reply == NULL) - { - __redisClusterSetError(cc, c->err, c->errstr); - return NULL; - } - - error_type = cluster_reply_error_type(reply); - if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL) - { - cc->retry_count ++; - if(cc->retry_count > cc->max_redirect_count) - { - __redisClusterSetError(cc, REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, - "too many cluster redirect"); - freeReplyObject(reply); - return NULL; - } - - switch(error_type) - { - case CLUSTER_ERR_MOVED: - freeReplyObject(reply); - reply = NULL; - ret = cluster_update_route(cc); - if(ret != REDIS_OK) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "route update error, please recreate redisClusterContext!"); - return NULL; - } - - goto retry; - - break; - case CLUSTER_ERR_ASK: - node = node_get_by_ask_error_reply(cc, reply); - if(node == NULL) - { - freeReplyObject(reply); - return NULL; - } - - freeReplyObject(reply); - reply = NULL; - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error"); - return NULL; - } - else if(c->err) - { - __redisClusterSetError(cc, c->err, c->errstr); - return NULL; - } - - reply = redisCommand(c, REDIS_COMMAND_ASKING); - if(reply == NULL) - { - __redisClusterSetError(cc, c->err, c->errstr); - return NULL; - } - - freeReplyObject(reply); - reply = NULL; - - goto ask_retry; - - break; - case CLUSTER_ERR_TRYAGAIN: - case CLUSTER_ERR_CROSSSLOT: - case CLUSTER_ERR_CLUSTERDOWN: - freeReplyObject(reply); - reply = NULL; - goto retry; - - break; - default: - - break; - } - } - - return reply; -} - -static int command_pre_fragment(redisClusterContext *cc, - struct cmd *command, hilist *commands) -{ - - struct keypos *kp, *sub_kp; - uint32_t key_count; - uint32_t i, j; - uint32_t idx; - uint32_t key_len; - int slot_num = -1; - struct cmd *sub_command; - struct cmd **sub_commands = NULL; - char num_str[12]; - uint8_t num_str_len; - - - if(command == NULL || commands == NULL) - { - goto done; - } - - key_count = hiarray_n(command->keys); - - sub_commands = hi_zalloc(REDIS_CLUSTER_SLOTS * sizeof(*sub_commands)); - if (sub_commands == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto done; - } - - command->frag_seq = hi_alloc(key_count * sizeof(*command->frag_seq)); - if(command->frag_seq == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto done; - } - - - for(i = 0; i < key_count; i ++) - { - kp = hiarray_get(command->keys, i); - - slot_num = keyHashSlot(kp->start, kp->end - kp->start); - - if(slot_num < 0 || slot_num >= REDIS_CLUSTER_SLOTS) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"keyHashSlot return error"); - goto done; - } - - if (sub_commands[slot_num] == NULL) { - sub_commands[slot_num] = command_get(); - if (sub_commands[slot_num] == NULL) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - slot_num = -1; - goto done; - } - } - - command->frag_seq[i] = sub_command = sub_commands[slot_num]; - - sub_command->narg++; - - sub_kp = hiarray_push(sub_command->keys); - if (sub_kp == NULL) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - slot_num = -1; - goto done; - } - - sub_kp->start = kp->start; - sub_kp->end = kp->end; - - key_len = (uint32_t)(kp->end - kp->start); - - sub_command->clen += key_len + uint_len(key_len); - - sub_command->slot_num = slot_num; - - if (command->type == CMD_REQ_REDIS_MSET) { - uint32_t len = 0; - char *p; - - for (p = sub_kp->end + 1; !isdigit(*p); p++){} - - p = sub_kp->end + 1; - while(!isdigit(*p)) - { - p ++; - } - - for (; isdigit(*p); p++) { - len = len * 10 + (uint32_t)(*p - '0'); - } - - len += CRLF_LEN * 2; - len += (p - sub_kp->end); - sub_kp->remain_len = len; - sub_command->clen += len; - } - } - - for (i = 0; i < REDIS_CLUSTER_SLOTS; i++) { /* prepend command header */ - sub_command = sub_commands[i]; - if (sub_command == NULL) { - continue; - } - - idx = 0; - if (command->type == CMD_REQ_REDIS_MGET) { - //"*%d\r\n$4\r\nmget\r\n" - - sub_command->clen += 5*sub_command->narg; - - sub_command->narg ++; - - hi_itoa(num_str, sub_command->narg); - num_str_len = (uint8_t)(strlen(num_str)); - - sub_command->clen += 13 + num_str_len; - - sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd)); - if(sub_command->cmd == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - slot_num = -1; - goto done; - } - - sub_command->cmd[idx++] = '*'; - memcpy(sub_command->cmd + idx, num_str, num_str_len); - idx += num_str_len; - memcpy(sub_command->cmd + idx, "\r\n$4\r\nmget\r\n", 12); - idx += 12; - - for(j = 0; j < hiarray_n(sub_command->keys); j ++) - { - kp = hiarray_get(sub_command->keys, j); - key_len = (uint32_t)(kp->end - kp->start); - hi_itoa(num_str, key_len); - num_str_len = strlen(num_str); - - sub_command->cmd[idx++] = '$'; - memcpy(sub_command->cmd + idx, num_str, num_str_len); - idx += num_str_len; - memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); - idx += CRLF_LEN; - memcpy(sub_command->cmd + idx, kp->start, key_len); - idx += key_len; - memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); - idx += CRLF_LEN; - } - } else if (command->type == CMD_REQ_REDIS_DEL) { - //"*%d\r\n$3\r\ndel\r\n" - - sub_command->clen += 5*sub_command->narg; - - sub_command->narg ++; - - hi_itoa(num_str, sub_command->narg); - num_str_len = (uint8_t)strlen(num_str); - - sub_command->clen += 12 + num_str_len; - - sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd)); - if(sub_command->cmd == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - slot_num = -1; - goto done; - } - - sub_command->cmd[idx++] = '*'; - memcpy(sub_command->cmd + idx, num_str, num_str_len); - idx += num_str_len; - memcpy(sub_command->cmd + idx, "\r\n$3\r\ndel\r\n", 11); - idx += 11; - - for(j = 0; j < hiarray_n(sub_command->keys); j ++) - { - kp = hiarray_get(sub_command->keys, j); - key_len = (uint32_t)(kp->end - kp->start); - hi_itoa(num_str, key_len); - num_str_len = strlen(num_str); - - sub_command->cmd[idx++] = '$'; - memcpy(sub_command->cmd + idx, num_str, num_str_len); - idx += num_str_len; - memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); - idx += CRLF_LEN; - memcpy(sub_command->cmd + idx, kp->start, key_len); - idx += key_len; - memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); - idx += CRLF_LEN; - } - } else if (command->type == CMD_REQ_REDIS_MSET) { - //"*%d\r\n$4\r\nmset\r\n" - - sub_command->clen += 3*sub_command->narg; - - sub_command->narg *= 2; - - sub_command->narg ++; - - hi_itoa(num_str, sub_command->narg); - num_str_len = (uint8_t)strlen(num_str); - - sub_command->clen += 13 + num_str_len; - - sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd)); - if(sub_command->cmd == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - slot_num = -1; - goto done; - } - - sub_command->cmd[idx++] = '*'; - memcpy(sub_command->cmd + idx, num_str, num_str_len); - idx += num_str_len; - memcpy(sub_command->cmd + idx, "\r\n$4\r\nmset\r\n", 12); - idx += 12; - - for(j = 0; j < hiarray_n(sub_command->keys); j ++) - { - kp = hiarray_get(sub_command->keys, j); - key_len = (uint32_t)(kp->end - kp->start); - hi_itoa(num_str, key_len); - num_str_len = strlen(num_str); - - sub_command->cmd[idx++] = '$'; - memcpy(sub_command->cmd + idx, num_str, num_str_len); - idx += num_str_len; - memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); - idx += CRLF_LEN; - memcpy(sub_command->cmd + idx, kp->start, key_len + kp->remain_len); - idx += key_len + kp->remain_len; - - } - } else { - NOT_REACHED(); - } - - //printf("len : %d\n", sub_command->clen); - //print_string_with_length_fix_CRLF(sub_command->cmd, sub_command->clen); - - sub_command->type = command->type; - - listAddNodeTail(commands, sub_command); - } - -done: - - if(sub_commands != NULL) - { - hi_free(sub_commands); - } - - if(slot_num >= 0 && commands != NULL - && listLength(commands) == 1) - { - listNode *list_node = listFirst(commands); - listDelNode(commands, list_node); - if(command->frag_seq) - { - hi_free(command->frag_seq); - command->frag_seq = NULL; - } - - command->slot_num = slot_num; - } - - return slot_num; -} - -static void *command_post_fragment(redisClusterContext *cc, - struct cmd *command, hilist *commands) -{ - struct cmd *sub_command; - listNode *list_node; - listIter *list_iter; - redisReply *reply, *sub_reply; - long long count = 0; - - list_iter = listGetIterator(commands, AL_START_HEAD); - while((list_node = listNext(list_iter)) != NULL) - { - sub_command = list_node->value; - reply = sub_command->reply; - if(reply == NULL) - { - return NULL; - } - else if(reply->type == REDIS_REPLY_ERROR) - { - return reply; - } - - if (command->type == CMD_REQ_REDIS_MGET) { - if(reply->type != REDIS_REPLY_ARRAY) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be array)"); - return NULL; - } - }else if(command->type == CMD_REQ_REDIS_DEL){ - if(reply->type != REDIS_REPLY_INTEGER) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be integer)"); - return NULL; - } - - count += reply->integer; - }else if(command->type == CMD_REQ_REDIS_MSET){ - if(reply->type != REDIS_REPLY_STATUS || - reply->len != 2 || strcmp(reply->str, REDIS_STATUS_OK) != 0) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be status and ok)"); - return NULL; - } - }else { - NOT_REACHED(); - } - } - - reply = hi_calloc(1,sizeof(*reply)); - - if (reply == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return NULL; - } - - if (command->type == CMD_REQ_REDIS_MGET) { - int i; - uint32_t key_count; - - reply->type = REDIS_REPLY_ARRAY; - - key_count = hiarray_n(command->keys); - - reply->elements = key_count; - reply->element = hi_calloc(key_count, sizeof(*reply)); - if (reply->element == NULL) { - freeReplyObject(reply); - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return NULL; - } - - for (i = key_count - 1; i >= 0; i--) { /* for each key */ - sub_reply = command->frag_seq[i]->reply; /* get it's reply */ - if (sub_reply == NULL) { - freeReplyObject(reply); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"sub reply is null"); - return NULL; - } - - if(sub_reply->type == REDIS_REPLY_STRING) - { - reply->element[i] = sub_reply; - } - else if(sub_reply->type == REDIS_REPLY_ARRAY) - { - if(sub_reply->elements == 0) - { - freeReplyObject(reply); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"sub reply elements error"); - return NULL; - } - - reply->element[i] = sub_reply->element[sub_reply->elements - 1]; - sub_reply->elements --; - } - } - }else if(command->type == CMD_REQ_REDIS_DEL){ - reply->type = REDIS_REPLY_INTEGER; - reply->integer = count; - }else if(command->type == CMD_REQ_REDIS_MSET){ - reply->type = REDIS_REPLY_STATUS; - uint32_t str_len = strlen(REDIS_STATUS_OK); - reply->str = hi_alloc((str_len + 1) * sizeof(char*)); - if(reply->str == NULL) - { - freeReplyObject(reply); - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return NULL; - } - - reply->len = str_len; - memcpy(reply->str, REDIS_STATUS_OK, str_len); - reply->str[str_len] = '\0'; - }else { - NOT_REACHED(); - } - - return reply; -} - -/* - * Split the command into subcommands by slot - * - * Returns slot_num - * If slot_num < 0 or slot_num >= REDIS_CLUSTER_SLOTS means this function runs error; - * Otherwise if the commands > 1 , slot_num is the last subcommand slot number. - */ -static int command_format_by_slot(redisClusterContext *cc, - struct cmd *command, hilist *commands) -{ - struct keypos *kp; - int key_count; - int slot_num = -1; - - if(cc == NULL || commands == NULL || - command == NULL || - command->cmd == NULL || command->clen <= 0) - { - goto done; - } - - - redis_parse_cmd(command); - if(command->result == CMD_PARSE_ENOMEM) - { - __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "Parse command error: out of memory"); - goto done; - } - else if(command->result != CMD_PARSE_OK) - { - __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, command->errstr); - goto done; - } - - key_count = hiarray_n(command->keys); - - if(key_count <= 0) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "No keys in command(must have keys for redis cluster mode)"); - goto done; - } - else if(key_count == 1) - { - kp = hiarray_get(command->keys, 0); - slot_num = keyHashSlot(kp->start, kp->end - kp->start); - command->slot_num = slot_num; - - goto done; - } - - slot_num = command_pre_fragment(cc, command, commands); - -done: - - return slot_num; -} - - -void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count) -{ - if(cc == NULL || max_redirect_count <= 0) - { - return; - } - - cc->max_redirect_count = max_redirect_count; -} - -void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len) { - redisReply *reply = NULL; - int slot_num; - struct cmd *command = NULL, *sub_command; - hilist *commands = NULL; - listNode *list_node; - listIter *list_iter = NULL; - - if(cc == NULL) - { - return NULL; - } - - if(cc->err) - { - cc->err = 0; - memset(cc->errstr, '\0', strlen(cc->errstr)); - } - - command = command_get(); - if(command == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return NULL; - } - - command->cmd = cmd; - command->clen = len; - - commands = listCreate(); - if(commands == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - commands->free = listCommandFree; - - slot_num = command_format_by_slot(cc, command, commands); - - if(slot_num < 0) - { - goto error; - } - else if(slot_num >= REDIS_CLUSTER_SLOTS) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range"); - goto error; - } - - //all keys belong to one slot - if(listLength(commands) == 0) - { - reply = redis_cluster_command_execute(cc, command); - goto done; - } - - ASSERT(listLength(commands) != 1); - - list_iter = listGetIterator(commands, AL_START_HEAD); - while((list_node = listNext(list_iter)) != NULL) - { - sub_command = list_node->value; - - reply = redis_cluster_command_execute(cc, sub_command); - if(reply == NULL) - { - goto error; - } - else if(reply->type == REDIS_REPLY_ERROR) - { - goto done; - } - - sub_command->reply = reply; - } - - reply = command_post_fragment(cc, command, commands); - -done: - - command->cmd = NULL; - command_destroy(command); - - if(commands != NULL) - { - listRelease(commands); - } - - if(list_iter != NULL) - { - listReleaseIterator(list_iter); - } - - cc->retry_count = 0; - - return reply; - -error: - - if(command != NULL) - { - command->cmd = NULL; - command_destroy(command); - } - - if(commands != NULL) - { - listRelease(commands); - } - - if(list_iter != NULL) - { - listReleaseIterator(list_iter); - } - - cc->retry_count = 0; - - return NULL; -} - -void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap) { - redisReply *reply; - char *cmd; - int len; - - if(cc == NULL) - { - return NULL; - } - - len = redisvFormatCommand(&cmd,format,ap); - - if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return NULL; - } else if (len == -2) { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); - return NULL; - } - - reply = redisClusterFormattedCommand(cc, cmd, len); - - free(cmd); - - return reply; -} - -void *redisClusterCommand(redisClusterContext *cc, const char *format, ...) { - va_list ap; - redisReply *reply = NULL; - - va_start(ap,format); - reply = redisClustervCommand(cc, format, ap); - va_end(ap); - - return reply; -} - -void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen) { - redisReply *reply = NULL; - char *cmd; - int len; - - len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); - if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return NULL; - } - - reply = redisClusterFormattedCommand(cc, cmd, len); - - free(cmd); - - return reply; -} - -int redisClusterAppendFormattedCommand(redisClusterContext *cc, - char *cmd, int len) { - int slot_num; - struct cmd *command = NULL, *sub_command; - hilist *commands = NULL; - listNode *list_node; - listIter *list_iter = NULL; - - if(cc->requests == NULL) - { - cc->requests = listCreate(); - if(cc->requests == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - cc->requests->free = listCommandFree; - } - - command = command_get(); - if(command == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - command->cmd = cmd; - command->clen = len; - - commands = listCreate(); - if(commands == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - commands->free = listCommandFree; - - slot_num = command_format_by_slot(cc, command, commands); - - if(slot_num < 0) - { - goto error; - } - else if(slot_num >= REDIS_CLUSTER_SLOTS) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range"); - goto error; - } - - //all keys belong to one slot - if(listLength(commands) == 0) - { - if(__redisClusterAppendCommand(cc, command) == REDIS_OK) - { - goto done; - } - else - { - goto error; - } - } - - ASSERT(listLength(commands) != 1); - - list_iter = listGetIterator(commands, AL_START_HEAD); - while((list_node = listNext(list_iter)) != NULL) - { - sub_command = list_node->value; - - if(__redisClusterAppendCommand(cc, sub_command) == REDIS_OK) - { - continue; - } - else - { - goto error; - } - } - -done: - - if(command->cmd != NULL) - { - command->cmd = NULL; - } - else - { - goto error; - } - - if(commands != NULL) - { - if(listLength(commands) > 0) - { - command->sub_commands = commands; - } - else - { - listRelease(commands); - } - } - - if(list_iter != NULL) - { - listReleaseIterator(list_iter); - } - - listAddNodeTail(cc->requests, command); - - return REDIS_OK; - -error: - - if(command != NULL) - { - command->cmd = NULL; - command_destroy(command); - } - - if(commands != NULL) - { - listRelease(commands); - } - - if(list_iter != NULL) - { - listReleaseIterator(list_iter); - } - - /* Attention: mybe here we must pop the - sub_commands that had append to the nodes. - But now we do not handle it. */ - - return REDIS_ERR; -} - - -int redisClustervAppendCommand(redisClusterContext *cc, - const char *format, va_list ap) { - int ret; - char *cmd; - int len; - - len = redisvFormatCommand(&cmd,format,ap); - if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } else if (len == -2) { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); - return REDIS_ERR; - } - - ret = redisClusterAppendFormattedCommand(cc, cmd, len); - - free(cmd); - - return ret; -} - -int redisClusterAppendCommand(redisClusterContext *cc, - const char *format, ...) { - - int ret; - va_list ap; - - if(cc == NULL || format == NULL) - { - return REDIS_ERR; - } - - va_start(ap,format); - ret = redisClustervAppendCommand(cc, format, ap); - va_end(ap); - - return ret; -} - -int redisClusterAppendCommandArgv(redisClusterContext *cc, - int argc, const char **argv, const size_t *argvlen) { - int ret; - char *cmd; - int len; - - len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); - if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - - ret = redisClusterAppendFormattedCommand(cc, cmd, len); - - free(cmd); - - return ret; -} - -static int redisCLusterSendAll(redisClusterContext *cc) -{ - dictIterator *di; - dictEntry *de; - struct cluster_node *node; - redisContext *c = NULL; - int wdone = 0; - - if(cc == NULL || cc->nodes == NULL) - { - return REDIS_ERR; - } - - di = dictGetIterator(cc->nodes); - while((de = dictNext(di)) != NULL) - { - node = dictGetEntryVal(de); - if(node == NULL) - { - continue; - } - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL) - { - continue; - } - - if (c->flags & REDIS_BLOCK) { - /* Write until done */ - do { - if (redisBufferWrite(c,&wdone) == REDIS_ERR) - { - dictReleaseIterator(di); - return REDIS_ERR; - } - } while (!wdone); - } - } - - dictReleaseIterator(di); - - return REDIS_OK; -} - -static int redisCLusterClearAll(redisClusterContext *cc) -{ - dictIterator *di; - dictEntry *de; - struct cluster_node *node; - redisContext *c = NULL; - - if (cc == NULL) { - return REDIS_ERR; - } - - if (cc->err) { - cc->err = 0; - memset(cc->errstr, '\0', strlen(cc->errstr)); - } - - if (cc->nodes == NULL) { - return REDIS_ERR; - } - di = dictGetIterator(cc->nodes); - while((de = dictNext(di)) != NULL) - { - node = dictGetEntryVal(de); - if(node == NULL) - { - continue; - } - - c = node->con; - if(c == NULL) - { - continue; - } - - redisFree(c); - node->con = NULL; - } - - dictReleaseIterator(di); - - return REDIS_OK; -} - -int redisClusterGetReply(redisClusterContext *cc, void **reply) { - - struct cmd *command, *sub_command; - hilist *commands = NULL; - listNode *list_command, *list_sub_command; - listIter *list_iter; - int slot_num; - void *sub_reply; - - if(cc == NULL || reply == NULL) - return REDIS_ERR; - - cc->err = 0; - cc->errstr[0] = '\0'; - - *reply = NULL; - - if (cc->requests == NULL) - return REDIS_ERR; - - list_command = listFirst(cc->requests); - - //no more reply - if(list_command == NULL) - { - *reply = NULL; - return REDIS_OK; - } - - command = list_command->value; - if(command == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "command in the requests list is null"); - goto error; - } - - slot_num = command->slot_num; - if(slot_num >= 0) - { - listDelNode(cc->requests, list_command); - return __redisClusterGetReply(cc, slot_num, reply); - } - - commands = command->sub_commands; - if(commands == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "sub_commands in command is null"); - goto error; - } - - ASSERT(listLength(commands) != 1); - - list_iter = listGetIterator(commands, AL_START_HEAD); - while((list_sub_command = listNext(list_iter)) != NULL) - { - sub_command = list_sub_command->value; - if(sub_command == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "sub_command is null"); - goto error; - } - - slot_num = sub_command->slot_num; - if(slot_num < 0) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "sub_command slot_num is less then zero"); - goto error; - } - - if(__redisClusterGetReply(cc, slot_num, &sub_reply) != REDIS_OK) - { - goto error; - } - - sub_command->reply = sub_reply; - } - - *reply = command_post_fragment(cc, command, commands); - if(*reply == NULL) - { - goto error; - } - - listDelNode(cc->requests, list_command); - return REDIS_OK; - -error: - - listDelNode(cc->requests, list_command); - return REDIS_ERR; -} - -void redisClusterReset(redisClusterContext *cc) -{ - int status; - void *reply; - - if(cc == NULL || cc->nodes == NULL) - { - return; - } - - if (cc->err) { - redisCLusterClearAll(cc); - } else { - redisCLusterSendAll(cc); - - do { - status = redisClusterGetReply(cc, &reply); - if (status == REDIS_OK) { - freeReplyObject(reply); - } else { - redisCLusterClearAll(cc); - break; - } - } while(reply != NULL); - } - - if(cc->requests) - { - listRelease(cc->requests); - cc->requests = NULL; - } - - if(cc->need_update_route) - { - status = cluster_update_route(cc); - if(status != REDIS_OK) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "route update error, please recreate redisClusterContext!"); - return; - } - cc->need_update_route = 0; - } -} - -/*############redis cluster async############*/ - -/* We want the error field to be accessible directly instead of requiring - * an indirection to the redisContext struct. */ -static void __redisClusterAsyncCopyError(redisClusterAsyncContext *acc) { - if (!acc) - return; - - redisClusterContext *cc = acc->cc; - acc->err = cc->err; - memcpy(acc->errstr, cc->errstr, 128); -} - -static void __redisClusterAsyncSetError(redisClusterAsyncContext *acc, - int type, const char *str) { - - size_t len; - - acc->err = type; - if (str != NULL) { - len = strlen(str); - len = len < (sizeof(acc->errstr)-1) ? len : (sizeof(acc->errstr)-1); - memcpy(acc->errstr,str,len); - acc->errstr[len] = '\0'; - } else { - /* Only REDIS_ERR_IO may lack a description! */ - assert(type == REDIS_ERR_IO); - __redis_strerror_r(errno, acc->errstr, sizeof(acc->errstr)); - } -} - -static redisClusterAsyncContext *redisClusterAsyncInitialize(redisClusterContext *cc) { - redisClusterAsyncContext *acc; - - if(cc == NULL) - { - return NULL; - } - - acc = hi_alloc(sizeof(redisClusterAsyncContext)); - if (acc == NULL) - return NULL; - - acc->cc = cc; - - acc->err = 0; - acc->data = NULL; - acc->adapter = NULL; - acc->attach_fn = NULL; - - acc->onConnect = NULL; - acc->onDisconnect = NULL; - - return acc; -} - -static cluster_async_data *cluster_async_data_get(void) -{ - cluster_async_data *cad; - - cad = hi_alloc(sizeof(cluster_async_data)); - if(cad == NULL) - { - return NULL; - } - - cad->acc = NULL; - cad->command = NULL; - cad->callback = NULL; - cad->privdata = NULL; - cad->retry_count = 0; - - return cad; -} - -static void cluster_async_data_free(cluster_async_data *cad) -{ - if(cad == NULL) - { - return; - } - - if(cad->command != NULL) - { - command_destroy(cad->command); - } - - hi_free(cad); - cad = NULL; -} - -static void unlinkAsyncContextAndNode(redisAsyncContext* ac) -{ - cluster_node *node; - - if (ac->data) { - node = (cluster_node *)(ac->data); - node->acon = NULL; - } -} - -redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, - cluster_node *node) -{ - redisAsyncContext *ac; - - if(node == NULL) - { - return NULL; - } - - ac = node->acon; - if(ac != NULL) - { - if (ac->c.err == 0) { - return ac; - } else { - NOT_REACHED(); - } - } - - if(node->host == NULL || node->port <= 0) - { - __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error"); - return NULL; - } - - ac = redisAsyncConnect(node->host, node->port); - if(ac == NULL) - { - __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error"); - return NULL; - } - - if(acc->adapter) - { - acc->attach_fn(ac, acc->adapter); - } - - if(acc->onConnect) - { - redisAsyncSetConnectCallback(ac, acc->onConnect); - } - - if(acc->onDisconnect) - { - redisAsyncSetDisconnectCallback(ac, acc->onDisconnect); - } - - ac->data = node; - ac->dataHandler = unlinkAsyncContextAndNode; - node->acon = ac; - - return ac; -} - -static redisAsyncContext *actx_get_after_update_route_by_slot( - redisClusterAsyncContext *acc, int slot_num) -{ - int ret; - redisClusterContext *cc; - redisAsyncContext *ac; - cluster_node *node; - - if(acc == NULL || slot_num < 0) - { - return NULL; - } - - cc = acc->cc; - if(cc == NULL) - { - return NULL; - } - - ret = cluster_update_route(cc); - if(ret != REDIS_OK) - { - __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, - "route update error, please recreate redisClusterContext!"); - return NULL; - } - - node = node_get_by_table(cc, (uint32_t)slot_num); - if(node == NULL) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, "node get by table error"); - return NULL; - } - - ac = actx_get_by_node(acc, node); - if(ac == NULL) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, "actx get by node error"); - return NULL; - } - else if(ac->err) - { - __redisClusterAsyncSetError(acc, ac->err, ac->errstr); - return NULL; - } - - return ac; -} - -redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags) { - - redisClusterContext *cc; - redisClusterAsyncContext *acc; - - cc = redisClusterConnectNonBlock(addrs, flags); - if(cc == NULL) - { - return NULL; - } - - acc = redisClusterAsyncInitialize(cc); - if (acc == NULL) { - redisClusterFree(cc); - return NULL; - } - - __redisClusterAsyncCopyError(acc); - - return acc; -} - - -int redisClusterAsyncSetConnectCallback( - redisClusterAsyncContext *acc, redisConnectCallback *fn) -{ - if (acc->onConnect == NULL) { - acc->onConnect = fn; - return REDIS_OK; - } - return REDIS_ERR; -} - -int redisClusterAsyncSetDisconnectCallback( - redisClusterAsyncContext *acc, redisDisconnectCallback *fn) -{ - if (acc->onDisconnect == NULL) { - acc->onDisconnect = fn; - return REDIS_OK; - } - return REDIS_ERR; -} - -static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *privdata) { - int ret; - redisReply *reply = r; - cluster_async_data *cad = privdata; - redisClusterAsyncContext *acc; - redisClusterContext *cc; - redisAsyncContext *ac_retry = NULL; - int error_type; - cluster_node *node; - struct cmd *command; - int64_t now, next; - - if(cad == NULL) - { - goto error; - } - - acc = cad->acc; - if(acc == NULL) - { - goto error; - } - - cc = acc->cc; - if(cc == NULL) - { - goto error; - } - - command = cad->command; - if(command == NULL) - { - goto error; - } - - if(reply == NULL) - { - //Note: - //I can't decide witch is the best way to deal with connect - //problem for hiredis cluster async api. - //But now the way is : when enough null reply for a node, - //we will update the route after the cluster node timeout. - //If you have a better idea, please contact with me. Thank you. - //My email: diguo58@gmail.com - - node = (cluster_node *)(ac->data); - ASSERT(node != NULL); - - __redisClusterAsyncSetError(acc, - ac->err, ac->errstr); - - if(cc->update_route_time != 0) - { - now = hi_usec_now(); - if(now >= cc->update_route_time) - { - ret = cluster_update_route(cc); - if(ret != REDIS_OK) - { - __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, - "route update error, please recreate redisClusterContext!"); - } - - cc->update_route_time = 0LL; - } - - goto done; - } - - node->failure_count ++; - if(node->failure_count > cc->max_redirect_count) - { - char *cluster_timeout_str; - int cluster_timeout_str_len; - int cluster_timeout; - - node->failure_count = 0; - if(cc->update_route_time != 0) - { - goto done; - } - - cluster_timeout_str = cluster_config_get(cc, - "cluster-node-timeout", &cluster_timeout_str_len); - if(cluster_timeout_str == NULL) - { - __redisClusterAsyncSetError(acc, - cc->err, cc->errstr); - goto done; - } - - cluster_timeout = hi_atoi(cluster_timeout_str, - cluster_timeout_str_len); - free(cluster_timeout_str); - if(cluster_timeout <= 0) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, - "cluster_timeout_str convert to integer error"); - goto done; - } - - now = hi_usec_now(); - if (now < 0) { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, - "get now usec time error"); - goto done; - } - - next = now + (cluster_timeout * 1000LL); - - cc->update_route_time = next; - - } - - goto done; - } - - error_type = cluster_reply_error_type(reply); - - if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL) - { - cad->retry_count ++; - if(cad->retry_count > cc->max_redirect_count) - { - cad->retry_count = 0; - __redisClusterAsyncSetError(acc, - REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, - "too many cluster redirect"); - goto done; - } - - switch(error_type) - { - case CLUSTER_ERR_MOVED: - ac_retry = actx_get_after_update_route_by_slot(acc, command->slot_num); - if(ac_retry == NULL) - { - goto done; - } - - break; - case CLUSTER_ERR_ASK: - node = node_get_by_ask_error_reply(cc, reply); - if(node == NULL) - { - __redisClusterAsyncSetError(acc, - cc->err, cc->errstr); - goto done; - } - - ac_retry = actx_get_by_node(acc, node); - if(ac_retry == NULL) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, "actx get by node error"); - goto done; - } - else if(ac_retry->err) - { - __redisClusterAsyncSetError(acc, - ac_retry->err, ac_retry->errstr); - goto done; - } - - ret = redisAsyncCommand(ac_retry, - NULL,NULL,REDIS_COMMAND_ASKING); - if(ret != REDIS_OK) - { - goto error; - } - - break; - case CLUSTER_ERR_TRYAGAIN: - case CLUSTER_ERR_CROSSSLOT: - case CLUSTER_ERR_CLUSTERDOWN: - ac_retry = ac; - - break; - default: - - goto done; - break; - } - - goto retry; - } - -done: - - if(acc->err) - { - cad->callback(acc, NULL, cad->privdata); - } - else - { - cad->callback(acc, r, cad->privdata); - } - - if(cc->err) - { - cc->err = 0; - memset(cc->errstr, '\0', strlen(cc->errstr)); - } - - if(acc->err) - { - acc->err = 0; - memset(acc->errstr, '\0', strlen(acc->errstr)); - } - - if(cad != NULL) - { - cluster_async_data_free(cad); - } - - return; - -retry: - - ret = redisAsyncFormattedCommand(ac_retry, - redisClusterAsyncCallback,cad,command->cmd,command->clen); - if(ret != REDIS_OK) - { - goto error; - } - - return; - -error: - - if(cad != NULL) - { - cluster_async_data_free(cad); - } -} - -int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, char *cmd, int len) { - - redisClusterContext *cc; - int status = REDIS_OK; - int slot_num; - cluster_node *node; - redisAsyncContext *ac; - struct cmd *command = NULL; - hilist *commands = NULL; - cluster_async_data *cad; - - if(acc == NULL) - { - return REDIS_ERR; - } - - cc = acc->cc; - - if(cc->err) - { - cc->err = 0; - memset(cc->errstr, '\0', strlen(cc->errstr)); - } - - if(acc->err) - { - acc->err = 0; - memset(acc->errstr, '\0', strlen(acc->errstr)); - } - - command = command_get(); - if(command == NULL) - { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - command->cmd = malloc(len*sizeof(*command->cmd)); - if(command->cmd == NULL) - { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - memcpy(command->cmd, cmd, len); - command->clen = len; - - commands = listCreate(); - if(commands == NULL) - { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - commands->free = listCommandFree; - - slot_num = command_format_by_slot(cc, command, commands); - - if(slot_num < 0) - { - __redisClusterAsyncSetError(acc, - cc->err, cc->errstr); - goto error; - } - else if(slot_num >= REDIS_CLUSTER_SLOTS) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER,"slot_num is out of range"); - goto error; - } - - //all keys not belong to one slot - if(listLength(commands) > 0) - { - ASSERT(listLength(commands) != 1); - - __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER, - "Asynchronous API now not support multi-key command"); - goto error; - } - - node = node_get_by_table(cc, (uint32_t) slot_num); - if(node == NULL) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, "node get by table error"); - goto error; - } - - ac = actx_get_by_node(acc, node); - if(ac == NULL) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, "actx get by node error"); - goto error; - } - else if(ac->err) - { - __redisClusterAsyncSetError(acc, ac->err, ac->errstr); - goto error; - } - - cad = cluster_async_data_get(); - if(cad == NULL) - { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - cad->acc = acc; - cad->command = command; - cad->callback = fn; - cad->privdata = privdata; - - status = redisAsyncFormattedCommand(ac, - redisClusterAsyncCallback,cad,cmd,len); - if(status != REDIS_OK) - { - goto error; - } - - if(commands != NULL) - { - listRelease(commands); - } - - return REDIS_OK; - -error: - - if(command != NULL) - { - command_destroy(command); - } - - if(commands != NULL) - { - listRelease(commands); - } - - return REDIS_ERR; -} - - -int redisClustervAsyncCommand(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap) { - int ret; - char *cmd; - int len; - - if(acc == NULL) - { - return REDIS_ERR; - } - - len = redisvFormatCommand(&cmd,format,ap); - if (len == -1) { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } else if (len == -2) { - __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER,"Invalid format string"); - return REDIS_ERR; - } - - ret = redisClusterAsyncFormattedCommand(acc, fn, privdata, cmd, len); - - free(cmd); - - return ret; -} - -int redisClusterAsyncCommand(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, const char *format, ...) { - int ret; - va_list ap; - - va_start(ap,format); - ret = redisClustervAsyncCommand(acc, fn, privdata, format, ap); - va_end(ap); - - return ret; -} - -int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { - int ret; - char *cmd; - int len; - - len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); - if (len == -1) { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - - ret = redisClusterAsyncFormattedCommand(acc, fn, privdata, cmd, len); - - free(cmd); - - return ret; -} - -void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) { - - redisClusterContext *cc; - redisAsyncContext *ac; - dictIterator *di; - dictEntry *de; - dict *nodes; - struct cluster_node *node; - - if(acc == NULL) - { - return; - } - - cc = acc->cc; - - nodes = cc->nodes; - - if(nodes == NULL) - { - return; - } - - di = dictGetIterator(nodes); - - while((de = dictNext(di)) != NULL) - { - node = dictGetEntryVal(de); - - ac = node->acon; - - if(ac == NULL || ac->err) - { - continue; - } - - redisAsyncDisconnect(ac); - - node->acon = NULL; - } -} - -void redisClusterAsyncFree(redisClusterAsyncContext *acc) -{ - redisClusterContext *cc; - - if(acc == NULL) - { - return; - } - - cc = acc->cc; - - redisClusterFree(cc); - - hi_free(acc); -} - diff --git a/ext/hiredis-vip-0.3.0/hircluster.h b/ext/hiredis-vip-0.3.0/hircluster.h deleted file mode 100644 index 5b9c5a358..000000000 --- a/ext/hiredis-vip-0.3.0/hircluster.h +++ /dev/null @@ -1,178 +0,0 @@ - -#ifndef __HIRCLUSTER_H -#define __HIRCLUSTER_H - -#include "hiredis.h" -#include "async.h" - -#define HIREDIS_VIP_MAJOR 0 -#define HIREDIS_VIP_MINOR 3 -#define HIREDIS_VIP_PATCH 0 - -#define REDIS_CLUSTER_SLOTS 16384 - -#define REDIS_ROLE_NULL 0 -#define REDIS_ROLE_MASTER 1 -#define REDIS_ROLE_SLAVE 2 - - -#define HIRCLUSTER_FLAG_NULL 0x0 -/* The flag to decide whether add slave node in - * redisClusterContext->nodes. This is set in the - * least significant bit of the flags field in - * redisClusterContext. (1000000000000) */ -#define HIRCLUSTER_FLAG_ADD_SLAVE 0x1000 -/* The flag to decide whether add open slot - * for master node. (10000000000000) */ -#define HIRCLUSTER_FLAG_ADD_OPENSLOT 0x2000 -/* The flag to decide whether get the route - * table by 'cluster slots' command. Default - * is 'cluster nodes' command.*/ -#define HIRCLUSTER_FLAG_ROUTE_USE_SLOTS 0x4000 - -struct dict; -struct hilist; - -typedef struct cluster_node -{ - sds name; - sds addr; - sds host; - int port; - uint8_t role; - uint8_t myself; /* myself ? */ - redisContext *con; - redisAsyncContext *acon; - struct hilist *slots; - struct hilist *slaves; - int failure_count; - void *data; /* Not used by hiredis */ - struct hiarray *migrating; /* copen_slot[] */ - struct hiarray *importing; /* copen_slot[] */ -}cluster_node; - -typedef struct cluster_slot -{ - uint32_t start; - uint32_t end; - cluster_node *node; /* master that this slot region belong to */ -}cluster_slot; - -typedef struct copen_slot -{ - uint32_t slot_num; /* slot number */ - int migrate; /* migrating or importing? */ - sds remote_name; /* name for the node that this slot migrating to/importing from */ - cluster_node *node; /* master that this slot belong to */ -}copen_slot; - -#ifdef __cplusplus -extern "C" { -#endif - -/* Context for a connection to Redis cluster */ -typedef struct redisClusterContext { - int err; /* Error flags, 0 when there is no error */ - char errstr[128]; /* String representation of error when applicable */ - sds ip; - int port; - - int flags; - - enum redisConnectionType connection_type; - struct timeval *timeout; - - struct hiarray *slots; - - struct dict *nodes; - cluster_node *table[REDIS_CLUSTER_SLOTS]; - - uint64_t route_version; - - int max_redirect_count; - int retry_count; - - struct hilist *requests; - - int need_update_route; - int64_t update_route_time; -} redisClusterContext; - -redisClusterContext *redisClusterConnect(const char *addrs, int flags); -redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, - const struct timeval tv, int flags); -redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); - -void redisClusterFree(redisClusterContext *cc); - -void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); - -void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len); -void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap); -void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); -void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); - -redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); - -int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len); -int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap); -int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); -int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); -int redisClusterGetReply(redisClusterContext *cc, void **reply); -void redisClusterReset(redisClusterContext *cc); - -int cluster_update_route(redisClusterContext *cc); -int test_cluster_update_route(redisClusterContext *cc); -struct dict *parse_cluster_nodes(redisClusterContext *cc, char *str, int str_len, int flags); -struct dict *parse_cluster_slots(redisClusterContext *cc, redisReply *reply, int flags); - - -/*############redis cluster async############*/ - -struct redisClusterAsyncContext; - -typedef int (adapterAttachFn)(redisAsyncContext*, void*); - -typedef void (redisClusterCallbackFn)(struct redisClusterAsyncContext*, void*, void*); - -/* Context for an async connection to Redis */ -typedef struct redisClusterAsyncContext { - - redisClusterContext *cc; - - /* Setup error flags so they can be used directly. */ - int err; - char errstr[128]; /* String representation of error when applicable */ - - /* Not used by hiredis */ - void *data; - - void *adapter; - adapterAttachFn *attach_fn; - - /* Called when either the connection is terminated due to an error or per - * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ - redisDisconnectCallback *onDisconnect; - - /* Called when the first write event was received. */ - redisConnectCallback *onConnect; - -} redisClusterAsyncContext; - -redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); -int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); -int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); -int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len); -int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap); -int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...); -int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); -void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); -void redisClusterAsyncFree(redisClusterAsyncContext *acc); - -redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, cluster_node *node); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/ext/hiredis-vip-0.3.0/hiredis.c b/ext/hiredis-vip-0.3.0/hiredis.c deleted file mode 100644 index 73d0251bc..000000000 --- a/ext/hiredis-vip-0.3.0/hiredis.c +++ /dev/null @@ -1,1021 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2014, Pieter Noordhuis - * Copyright (c) 2015, Matt Stancliff , - * Jan-Erik Rediger - * - * 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 "fmacros.h" -#include -#include -#include -#include -#include -#include - -#include "hiredis.h" -#include "net.h" -#include "sds.h" - -static redisReply *createReplyObject(int type); -static void *createStringObject(const redisReadTask *task, char *str, size_t len); -static void *createArrayObject(const redisReadTask *task, int elements); -static void *createIntegerObject(const redisReadTask *task, long long value); -static void *createNilObject(const redisReadTask *task); - -/* Default set of functions to build the reply. Keep in mind that such a - * function returning NULL is interpreted as OOM. */ -static redisReplyObjectFunctions defaultFunctions = { - createStringObject, - createArrayObject, - createIntegerObject, - createNilObject, - freeReplyObject -}; - -/* Create a reply object */ -static redisReply *createReplyObject(int type) { - redisReply *r = calloc(1,sizeof(*r)); - - if (r == NULL) - return NULL; - - r->type = type; - return r; -} - -/* Free a reply object */ -void freeReplyObject(void *reply) { - redisReply *r = reply; - size_t j; - - if (r == NULL) - return; - - switch(r->type) { - case REDIS_REPLY_INTEGER: - break; /* Nothing to free */ - case REDIS_REPLY_ARRAY: - if (r->element != NULL) { - for (j = 0; j < r->elements; j++) - if (r->element[j] != NULL) - freeReplyObject(r->element[j]); - free(r->element); - } - break; - case REDIS_REPLY_ERROR: - case REDIS_REPLY_STATUS: - case REDIS_REPLY_STRING: - if (r->str != NULL) - free(r->str); - break; - } - free(r); -} - -static void *createStringObject(const redisReadTask *task, char *str, size_t len) { - redisReply *r, *parent; - char *buf; - - r = createReplyObject(task->type); - if (r == NULL) - return NULL; - - buf = malloc(len+1); - if (buf == NULL) { - freeReplyObject(r); - return NULL; - } - - assert(task->type == REDIS_REPLY_ERROR || - task->type == REDIS_REPLY_STATUS || - task->type == REDIS_REPLY_STRING); - - /* Copy string value */ - memcpy(buf,str,len); - buf[len] = '\0'; - r->str = buf; - r->len = len; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); - parent->element[task->idx] = r; - } - return r; -} - -static void *createArrayObject(const redisReadTask *task, int elements) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_ARRAY); - if (r == NULL) - return NULL; - - if (elements > 0) { - r->element = calloc(elements,sizeof(redisReply*)); - if (r->element == NULL) { - freeReplyObject(r); - return NULL; - } - } - - r->elements = elements; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); - parent->element[task->idx] = r; - } - return r; -} - -static void *createIntegerObject(const redisReadTask *task, long long value) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_INTEGER); - if (r == NULL) - return NULL; - - r->integer = value; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); - parent->element[task->idx] = r; - } - return r; -} - -static void *createNilObject(const redisReadTask *task) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_NIL); - if (r == NULL) - return NULL; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); - parent->element[task->idx] = r; - } - return r; -} - -/* Return the number of digits of 'v' when converted to string in radix 10. - * Implementation borrowed from link in redis/src/util.c:string2ll(). */ -static uint32_t countDigits(uint64_t v) { - uint32_t result = 1; - for (;;) { - if (v < 10) return result; - if (v < 100) return result + 1; - if (v < 1000) return result + 2; - if (v < 10000) return result + 3; - v /= 10000U; - result += 4; - } -} - -/* Helper that calculates the bulk length given a certain string length. */ -static size_t bulklen(size_t len) { - return 1+countDigits(len)+2+len+2; -} - -int redisvFormatCommand(char **target, const char *format, va_list ap) { - const char *c = format; - char *cmd = NULL; /* final command */ - int pos; /* position in final command */ - sds curarg, newarg; /* current argument */ - int touched = 0; /* was the current argument touched? */ - char **curargv = NULL, **newargv = NULL; - int argc = 0; - int totlen = 0; - int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */ - int j; - - /* Abort if there is not target to set */ - if (target == NULL) - return -1; - - /* Build the command string accordingly to protocol */ - curarg = sdsempty(); - if (curarg == NULL) - return -1; - - while(*c != '\0') { - if (*c != '%' || c[1] == '\0') { - if (*c == ' ') { - if (touched) { - newargv = realloc(curargv,sizeof(char*)*(argc+1)); - if (newargv == NULL) goto memory_err; - curargv = newargv; - curargv[argc++] = curarg; - totlen += bulklen(sdslen(curarg)); - - /* curarg is put in argv so it can be overwritten. */ - curarg = sdsempty(); - if (curarg == NULL) goto memory_err; - touched = 0; - } - } else { - newarg = sdscatlen(curarg,c,1); - if (newarg == NULL) goto memory_err; - curarg = newarg; - touched = 1; - } - } else { - char *arg; - size_t size; - - /* Set newarg so it can be checked even if it is not touched. */ - newarg = curarg; - - switch(c[1]) { - case 's': - arg = va_arg(ap,char*); - size = strlen(arg); - if (size > 0) - newarg = sdscatlen(curarg,arg,size); - break; - case 'b': - arg = va_arg(ap,char*); - size = va_arg(ap,size_t); - if (size > 0) - newarg = sdscatlen(curarg,arg,size); - break; - case '%': - newarg = sdscat(curarg,"%"); - break; - default: - /* Try to detect printf format */ - { - static const char intfmts[] = "diouxX"; - static const char flags[] = "#0-+ "; - char _format[16]; - const char *_p = c+1; - size_t _l = 0; - va_list _cpy; - - /* Flags */ - while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; - - /* Field width */ - while (*_p != '\0' && isdigit(*_p)) _p++; - - /* Precision */ - if (*_p == '.') { - _p++; - while (*_p != '\0' && isdigit(*_p)) _p++; - } - - /* Copy va_list before consuming with va_arg */ - va_copy(_cpy,ap); - - /* Integer conversion (without modifiers) */ - if (strchr(intfmts,*_p) != NULL) { - va_arg(ap,int); - goto fmt_valid; - } - - /* Double conversion (without modifiers) */ - if (strchr("eEfFgGaA",*_p) != NULL) { - va_arg(ap,double); - goto fmt_valid; - } - - /* Size: char */ - if (_p[0] == 'h' && _p[1] == 'h') { - _p += 2; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,int); /* char gets promoted to int */ - goto fmt_valid; - } - goto fmt_invalid; - } - - /* Size: short */ - if (_p[0] == 'h') { - _p += 1; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,int); /* short gets promoted to int */ - goto fmt_valid; - } - goto fmt_invalid; - } - - /* Size: long long */ - if (_p[0] == 'l' && _p[1] == 'l') { - _p += 2; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,long long); - goto fmt_valid; - } - goto fmt_invalid; - } - - /* Size: long */ - if (_p[0] == 'l') { - _p += 1; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,long); - goto fmt_valid; - } - goto fmt_invalid; - } - - fmt_invalid: - va_end(_cpy); - goto format_err; - - fmt_valid: - _l = (_p+1)-c; - if (_l < sizeof(_format)-2) { - memcpy(_format,c,_l); - _format[_l] = '\0'; - newarg = sdscatvprintf(curarg,_format,_cpy); - - /* Update current position (note: outer blocks - * increment c twice so compensate here) */ - c = _p-1; - } - - va_end(_cpy); - break; - } - } - - if (newarg == NULL) goto memory_err; - curarg = newarg; - - touched = 1; - c++; - } - c++; - } - - /* Add the last argument if needed */ - if (touched) { - newargv = realloc(curargv,sizeof(char*)*(argc+1)); - if (newargv == NULL) goto memory_err; - curargv = newargv; - curargv[argc++] = curarg; - totlen += bulklen(sdslen(curarg)); - } else { - sdsfree(curarg); - } - - /* Clear curarg because it was put in curargv or was free'd. */ - curarg = NULL; - - /* Add bytes needed to hold multi bulk count */ - totlen += 1+countDigits(argc)+2; - - /* Build the command at protocol level */ - cmd = malloc(totlen+1); - if (cmd == NULL) goto memory_err; - - pos = sprintf(cmd,"*%d\r\n",argc); - for (j = 0; j < argc; j++) { - pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); - memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); - pos += sdslen(curargv[j]); - sdsfree(curargv[j]); - cmd[pos++] = '\r'; - cmd[pos++] = '\n'; - } - assert(pos == totlen); - cmd[pos] = '\0'; - - free(curargv); - *target = cmd; - return totlen; - -format_err: - error_type = -2; - goto cleanup; - -memory_err: - error_type = -1; - goto cleanup; - -cleanup: - if (curargv) { - while(argc--) - sdsfree(curargv[argc]); - free(curargv); - } - - sdsfree(curarg); - - /* No need to check cmd since it is the last statement that can fail, - * but do it anyway to be as defensive as possible. */ - if (cmd != NULL) - free(cmd); - - return error_type; -} - -/* Format a command according to the Redis protocol. This function - * takes a format similar to printf: - * - * %s represents a C null terminated string you want to interpolate - * %b represents a binary safe string - * - * When using %b you need to provide both the pointer to the string - * and the length in bytes as a size_t. Examples: - * - * len = redisFormatCommand(target, "GET %s", mykey); - * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); - */ -int redisFormatCommand(char **target, const char *format, ...) { - va_list ap; - int len; - va_start(ap,format); - len = redisvFormatCommand(target,format,ap); - va_end(ap); - - /* The API says "-1" means bad result, but we now also return "-2" in some - * cases. Force the return value to always be -1. */ - if (len < 0) - len = -1; - - return len; -} - -/* Format a command according to the Redis protocol using an sds string and - * sdscatfmt for the processing of arguments. This function takes the - * number of arguments, an array with arguments and an array with their - * lengths. If the latter is set to NULL, strlen will be used to compute the - * argument lengths. - */ -int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, - const size_t *argvlen) -{ - sds cmd; - unsigned long long totlen; - int j; - size_t len; - - /* Abort on a NULL target */ - if (target == NULL) - return -1; - - /* Calculate our total size */ - totlen = 1+countDigits(argc)+2; - for (j = 0; j < argc; j++) { - len = argvlen ? argvlen[j] : strlen(argv[j]); - totlen += bulklen(len); - } - - /* Use an SDS string for command construction */ - cmd = sdsempty(); - if (cmd == NULL) - return -1; - - /* We already know how much storage we need */ - cmd = sdsMakeRoomFor(cmd, totlen); - if (cmd == NULL) - return -1; - - /* Construct command */ - cmd = sdscatfmt(cmd, "*%i\r\n", argc); - for (j=0; j < argc; j++) { - len = argvlen ? argvlen[j] : strlen(argv[j]); - cmd = sdscatfmt(cmd, "$%T\r\n", len); - cmd = sdscatlen(cmd, argv[j], len); - cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); - } - - assert(sdslen(cmd)==totlen); - - *target = cmd; - return totlen; -} - -void redisFreeSdsCommand(sds cmd) { - sdsfree(cmd); -} - -/* Format a command according to the Redis protocol. This function takes the - * number of arguments, an array with arguments and an array with their - * lengths. If the latter is set to NULL, strlen will be used to compute the - * argument lengths. - */ -int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { - char *cmd = NULL; /* final command */ - int pos; /* position in final command */ - size_t len; - int totlen, j; - - /* Abort on a NULL target */ - if (target == NULL) - return -1; - - /* Calculate number of bytes needed for the command */ - totlen = 1+countDigits(argc)+2; - for (j = 0; j < argc; j++) { - len = argvlen ? argvlen[j] : strlen(argv[j]); - totlen += bulklen(len); - } - - /* Build the command at protocol level */ - cmd = malloc(totlen+1); - if (cmd == NULL) - return -1; - - pos = sprintf(cmd,"*%d\r\n",argc); - for (j = 0; j < argc; j++) { - len = argvlen ? argvlen[j] : strlen(argv[j]); - pos += sprintf(cmd+pos,"$%zu\r\n",len); - memcpy(cmd+pos,argv[j],len); - pos += len; - cmd[pos++] = '\r'; - cmd[pos++] = '\n'; - } - assert(pos == totlen); - cmd[pos] = '\0'; - - *target = cmd; - return totlen; -} - -void redisFreeCommand(char *cmd) { - free(cmd); -} - -void __redisSetError(redisContext *c, int type, const char *str) { - size_t len; - - c->err = type; - if (str != NULL) { - len = strlen(str); - len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); - memcpy(c->errstr,str,len); - c->errstr[len] = '\0'; - } else { - /* Only REDIS_ERR_IO may lack a description! */ - assert(type == REDIS_ERR_IO); - __redis_strerror_r(errno, c->errstr, sizeof(c->errstr)); - } -} - -redisReader *redisReaderCreate(void) { - return redisReaderCreateWithFunctions(&defaultFunctions); -} - -static redisContext *redisContextInit(void) { - redisContext *c; - - c = calloc(1,sizeof(redisContext)); - if (c == NULL) - return NULL; - - c->err = 0; - c->errstr[0] = '\0'; - c->obuf = sdsempty(); - c->reader = redisReaderCreate(); - c->tcp.host = NULL; - c->tcp.source_addr = NULL; - c->unix_sock.path = NULL; - c->timeout = NULL; - - if (c->obuf == NULL || c->reader == NULL) { - redisFree(c); - return NULL; - } - - return c; -} - -void redisFree(redisContext *c) { - if (c == NULL) - return; - if (c->fd > 0) - close(c->fd); - if (c->obuf != NULL) - sdsfree(c->obuf); - if (c->reader != NULL) - redisReaderFree(c->reader); - if (c->tcp.host) - free(c->tcp.host); - if (c->tcp.source_addr) - free(c->tcp.source_addr); - if (c->unix_sock.path) - free(c->unix_sock.path); - if (c->timeout) - free(c->timeout); - free(c); -} - -int redisFreeKeepFd(redisContext *c) { - int fd = c->fd; - c->fd = -1; - redisFree(c); - return fd; -} - -int redisReconnect(redisContext *c) { - c->err = 0; - memset(c->errstr, '\0', strlen(c->errstr)); - - if (c->fd > 0) { - close(c->fd); - } - - sdsfree(c->obuf); - redisReaderFree(c->reader); - - c->obuf = sdsempty(); - c->reader = redisReaderCreate(); - - if (c->connection_type == REDIS_CONN_TCP) { - return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port, - c->timeout, c->tcp.source_addr); - } else if (c->connection_type == REDIS_CONN_UNIX) { - return redisContextConnectUnix(c, c->unix_sock.path, c->timeout); - } else { - /* Something bad happened here and shouldn't have. There isn't - enough information in the context to reconnect. */ - __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect"); - } - - return REDIS_ERR; -} - -/* Connect to a Redis instance. On error the field error in the returned - * context will be set to the return value of the error function. - * When no set of reply functions is given, the default set will be used. */ -redisContext *redisConnect(const char *ip, int port) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,NULL); - return c; -} - -redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,&tv); - return c; -} - -redisContext *redisConnectNonBlock(const char *ip, int port) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags &= ~REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,NULL); - return c; -} - -redisContext *redisConnectBindNonBlock(const char *ip, int port, - const char *source_addr) { - redisContext *c = redisContextInit(); - c->flags &= ~REDIS_BLOCK; - redisContextConnectBindTcp(c,ip,port,NULL,source_addr); - return c; -} - -redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, - const char *source_addr) { - redisContext *c = redisContextInit(); - c->flags &= ~REDIS_BLOCK; - c->flags |= REDIS_REUSEADDR; - redisContextConnectBindTcp(c,ip,port,NULL,source_addr); - return c; -} - -redisContext *redisConnectUnix(const char *path) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectUnix(c,path,NULL); - return c; -} - -redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectUnix(c,path,&tv); - return c; -} - -redisContext *redisConnectUnixNonBlock(const char *path) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags &= ~REDIS_BLOCK; - redisContextConnectUnix(c,path,NULL); - return c; -} - -redisContext *redisConnectFd(int fd) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->fd = fd; - c->flags |= REDIS_BLOCK | REDIS_CONNECTED; - return c; -} - -/* Set read/write timeout on a blocking socket. */ -int redisSetTimeout(redisContext *c, const struct timeval tv) { - if (c->flags & REDIS_BLOCK) - return redisContextSetTimeout(c,tv); - return REDIS_ERR; -} - -/* Enable connection KeepAlive. */ -int redisEnableKeepAlive(redisContext *c) { - if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK) - return REDIS_ERR; - return REDIS_OK; -} - -/* Use this function to handle a read event on the descriptor. It will try - * and read some bytes from the socket and feed them to the reply parser. - * - * After this function is called, you may use redisContextReadReply to - * see if there is a reply available. */ -int redisBufferRead(redisContext *c) { - char buf[1024*16]; - int nread; - - /* Return early when the context has seen an error. */ - if (c->err) - return REDIS_ERR; - - nread = read(c->fd,buf,sizeof(buf)); - if (nread == -1) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - } else { - __redisSetError(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } - } else if (nread == 0) { - __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); - return REDIS_ERR; - } else { - if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { - __redisSetError(c,c->reader->err,c->reader->errstr); - return REDIS_ERR; - } - } - return REDIS_OK; -} - -/* Write the output buffer to the socket. - * - * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was - * succesfully written to the socket. When the buffer is empty after the - * write operation, "done" is set to 1 (if given). - * - * Returns REDIS_ERR if an error occured trying to write and sets - * c->errstr to hold the appropriate error string. - */ -int redisBufferWrite(redisContext *c, int *done) { - int nwritten; - - /* Return early when the context has seen an error. */ - if (c->err) - return REDIS_ERR; - - if (sdslen(c->obuf) > 0) { - nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); - if (nwritten == -1) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - } else { - __redisSetError(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } - } else if (nwritten > 0) { - if (nwritten == (signed)sdslen(c->obuf)) { - sdsfree(c->obuf); - c->obuf = sdsempty(); - } else { - sdsrange(c->obuf,nwritten,-1); - } - } - } - if (done != NULL) *done = (sdslen(c->obuf) == 0); - return REDIS_OK; -} - -/* Internal helper function to try and get a reply from the reader, - * or set an error in the context otherwise. */ -int redisGetReplyFromReader(redisContext *c, void **reply) { - if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { - __redisSetError(c,c->reader->err,c->reader->errstr); - return REDIS_ERR; - } - return REDIS_OK; -} - -int redisGetReply(redisContext *c, void **reply) { - int wdone = 0; - void *aux = NULL; - - /* Try to read pending replies */ - if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) - return REDIS_ERR; - - /* For the blocking context, flush output buffer and read reply */ - if (aux == NULL && c->flags & REDIS_BLOCK) { - /* Write until done */ - do { - if (redisBufferWrite(c,&wdone) == REDIS_ERR) - return REDIS_ERR; - } while (!wdone); - - /* Read until there is a reply */ - do { - if (redisBufferRead(c) == REDIS_ERR) - return REDIS_ERR; - if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) - return REDIS_ERR; - } while (aux == NULL); - } - - /* Set reply object */ - if (reply != NULL) *reply = aux; - return REDIS_OK; -} - - -/* Helper function for the redisAppendCommand* family of functions. - * - * Write a formatted command to the output buffer. When this family - * is used, you need to call redisGetReply yourself to retrieve - * the reply (or replies in pub/sub). - */ -int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { - sds newbuf; - - newbuf = sdscatlen(c->obuf,cmd,len); - if (newbuf == NULL) { - __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - - c->obuf = newbuf; - return REDIS_OK; -} - -int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) { - - if (__redisAppendCommand(c, cmd, len) != REDIS_OK) { - return REDIS_ERR; - } - - return REDIS_OK; -} - -int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { - char *cmd; - int len; - - len = redisvFormatCommand(&cmd,format,ap); - if (len == -1) { - __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } else if (len == -2) { - __redisSetError(c,REDIS_ERR_OTHER,"Invalid format string"); - return REDIS_ERR; - } - - if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { - free(cmd); - return REDIS_ERR; - } - - free(cmd); - return REDIS_OK; -} - -int redisAppendCommand(redisContext *c, const char *format, ...) { - va_list ap; - int ret; - - va_start(ap,format); - ret = redisvAppendCommand(c,format,ap); - va_end(ap); - return ret; -} - -int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { - sds cmd; - int len; - - len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); - if (len == -1) { - __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - - if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { - sdsfree(cmd); - return REDIS_ERR; - } - - sdsfree(cmd); - return REDIS_OK; -} - -/* Helper function for the redisCommand* family of functions. - * - * Write a formatted command to the output buffer. If the given context is - * blocking, immediately read the reply into the "reply" pointer. When the - * context is non-blocking, the "reply" pointer will not be used and the - * command is simply appended to the write buffer. - * - * Returns the reply when a reply was succesfully retrieved. Returns NULL - * otherwise. When NULL is returned in a blocking context, the error field - * in the context will be set. - */ -static void *__redisBlockForReply(redisContext *c) { - void *reply; - - if (c->flags & REDIS_BLOCK) { - if (redisGetReply(c,&reply) != REDIS_OK) - return NULL; - return reply; - } - return NULL; -} - -void *redisvCommand(redisContext *c, const char *format, va_list ap) { - if (redisvAppendCommand(c,format,ap) != REDIS_OK) - return NULL; - return __redisBlockForReply(c); -} - -void *redisCommand(redisContext *c, const char *format, ...) { - va_list ap; - void *reply = NULL; - va_start(ap,format); - reply = redisvCommand(c,format,ap); - va_end(ap); - return reply; -} - -void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { - if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) - return NULL; - return __redisBlockForReply(c); -} diff --git a/ext/hiredis-vip-0.3.0/hiutil.c b/ext/hiredis-vip-0.3.0/hiutil.c deleted file mode 100644 index d10cdacea..000000000 --- a/ext/hiredis-vip-0.3.0/hiutil.c +++ /dev/null @@ -1,554 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - - -#include "hiutil.h" - -#ifdef HI_HAVE_BACKTRACE -# include -#endif - -int -hi_set_blocking(int sd) -{ - int flags; - - flags = fcntl(sd, F_GETFL, 0); - if (flags < 0) { - return flags; - } - - return fcntl(sd, F_SETFL, flags & ~O_NONBLOCK); -} - -int -hi_set_nonblocking(int sd) -{ - int flags; - - flags = fcntl(sd, F_GETFL, 0); - if (flags < 0) { - return flags; - } - - return fcntl(sd, F_SETFL, flags | O_NONBLOCK); -} - -int -hi_set_reuseaddr(int sd) -{ - int reuse; - socklen_t len; - - reuse = 1; - len = sizeof(reuse); - - return setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &reuse, len); -} - -/* - * Disable Nagle algorithm on TCP socket. - * - * This option helps to minimize transmit latency by disabling coalescing - * of data to fill up a TCP segment inside the kernel. Sockets with this - * option must use readv() or writev() to do data transfer in bulk and - * hence avoid the overhead of small packets. - */ -int -hi_set_tcpnodelay(int sd) -{ - int nodelay; - socklen_t len; - - nodelay = 1; - len = sizeof(nodelay); - - return setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, &nodelay, len); -} - -int -hi_set_linger(int sd, int timeout) -{ - struct linger linger; - socklen_t len; - - linger.l_onoff = 1; - linger.l_linger = timeout; - - len = sizeof(linger); - - return setsockopt(sd, SOL_SOCKET, SO_LINGER, &linger, len); -} - -int -hi_set_sndbuf(int sd, int size) -{ - socklen_t len; - - len = sizeof(size); - - return setsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, len); -} - -int -hi_set_rcvbuf(int sd, int size) -{ - socklen_t len; - - len = sizeof(size); - - return setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, len); -} - -int -hi_get_soerror(int sd) -{ - int status, err; - socklen_t len; - - err = 0; - len = sizeof(err); - - status = getsockopt(sd, SOL_SOCKET, SO_ERROR, &err, &len); - if (status == 0) { - errno = err; - } - - return status; -} - -int -hi_get_sndbuf(int sd) -{ - int status, size; - socklen_t len; - - size = 0; - len = sizeof(size); - - status = getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, &len); - if (status < 0) { - return status; - } - - return size; -} - -int -hi_get_rcvbuf(int sd) -{ - int status, size; - socklen_t len; - - size = 0; - len = sizeof(size); - - status = getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, &len); - if (status < 0) { - return status; - } - - return size; -} - -int -_hi_atoi(uint8_t *line, size_t n) -{ - int value; - - if (n == 0) { - return -1; - } - - for (value = 0; n--; line++) { - if (*line < '0' || *line > '9') { - return -1; - } - - value = value * 10 + (*line - '0'); - } - - if (value < 0) { - return -1; - } - - return value; -} - -void -_hi_itoa(uint8_t *s, int num) -{ - uint8_t c; - uint8_t sign = 0; - - if(s == NULL) - { - return; - } - - uint32_t len, i; - len = 0; - - if(num < 0) - { - sign = 1; - num = abs(num); - } - else if(num == 0) - { - s[len++] = '0'; - return; - } - - while(num % 10 || num /10) - { - c = num %10 + '0'; - num = num /10; - s[len+1] = s[len]; - s[len] = c; - len ++; - } - - if(sign == 1) - { - s[len++] = '-'; - } - - s[len] = '\0'; - - for(i = 0; i < len/2; i ++) - { - c = s[i]; - s[i] = s[len - i -1]; - s[len - i -1] = c; - } - -} - - -int -hi_valid_port(int n) -{ - if (n < 1 || n > UINT16_MAX) { - return 0; - } - - return 1; -} - -int _uint_len(uint32_t num) -{ - int n = 0; - - if(num == 0) - { - return 1; - } - - while(num != 0) - { - n ++; - num /= 10; - } - - return n; -} - -void * -_hi_alloc(size_t size, const char *name, int line) -{ - void *p; - - ASSERT(size != 0); - - p = malloc(size); - - if(name == NULL && line == 1) - { - - } - - return p; -} - -void * -_hi_zalloc(size_t size, const char *name, int line) -{ - void *p; - - p = _hi_alloc(size, name, line); - if (p != NULL) { - memset(p, 0, size); - } - - return p; -} - -void * -_hi_calloc(size_t nmemb, size_t size, const char *name, int line) -{ - return _hi_zalloc(nmemb * size, name, line); -} - -void * -_hi_realloc(void *ptr, size_t size, const char *name, int line) -{ - void *p; - - ASSERT(size != 0); - - p = realloc(ptr, size); - - if(name == NULL && line == 1) - { - - } - - return p; -} - -void -_hi_free(void *ptr, const char *name, int line) -{ - ASSERT(ptr != NULL); - - if(name == NULL && line == 1) - { - - } - - free(ptr); -} - -void -hi_stacktrace(int skip_count) -{ - if(skip_count > 0) - { - - } - -#ifdef HI_HAVE_BACKTRACE - void *stack[64]; - char **symbols; - int size, i, j; - - size = backtrace(stack, 64); - symbols = backtrace_symbols(stack, size); - if (symbols == NULL) { - return; - } - - skip_count++; /* skip the current frame also */ - - for (i = skip_count, j = 0; i < size; i++, j++) { - printf("[%d] %s\n", j, symbols[i]); - } - - free(symbols); -#endif -} - -void -hi_stacktrace_fd(int fd) -{ - if(fd > 0) - { - - } -#ifdef HI_HAVE_BACKTRACE - void *stack[64]; - int size; - - size = backtrace(stack, 64); - backtrace_symbols_fd(stack, size, fd); -#endif -} - -void -hi_assert(const char *cond, const char *file, int line, int panic) -{ - - printf("File: %s Line: %d: %s\n", file, line, cond); - - if (panic) { - hi_stacktrace(1); - abort(); - } - abort(); -} - -int -_vscnprintf(char *buf, size_t size, const char *fmt, va_list args) -{ - int n; - - n = vsnprintf(buf, size, fmt, args); - - /* - * The return value is the number of characters which would be written - * into buf not including the trailing '\0'. If size is == 0 the - * function returns 0. - * - * On error, the function also returns 0. This is to allow idiom such - * as len += _vscnprintf(...) - * - * See: http://lwn.net/Articles/69419/ - */ - if (n <= 0) { - return 0; - } - - if (n < (int) size) { - return n; - } - - return (int)(size - 1); -} - -int -_scnprintf(char *buf, size_t size, const char *fmt, ...) -{ - va_list args; - int n; - - va_start(args, fmt); - n = _vscnprintf(buf, size, fmt, args); - va_end(args); - - return n; -} - -/* - * Send n bytes on a blocking descriptor - */ -ssize_t -_hi_sendn(int sd, const void *vptr, size_t n) -{ - size_t nleft; - ssize_t nsend; - const char *ptr; - - ptr = vptr; - nleft = n; - while (nleft > 0) { - nsend = send(sd, ptr, nleft, 0); - if (nsend < 0) { - if (errno == EINTR) { - continue; - } - return nsend; - } - if (nsend == 0) { - return -1; - } - - nleft -= (size_t)nsend; - ptr += nsend; - } - - return (ssize_t)n; -} - -/* - * Recv n bytes from a blocking descriptor - */ -ssize_t -_hi_recvn(int sd, void *vptr, size_t n) -{ - size_t nleft; - ssize_t nrecv; - char *ptr; - - ptr = vptr; - nleft = n; - while (nleft > 0) { - nrecv = recv(sd, ptr, nleft, 0); - if (nrecv < 0) { - if (errno == EINTR) { - continue; - } - return nrecv; - } - if (nrecv == 0) { - break; - } - - nleft -= (size_t)nrecv; - ptr += nrecv; - } - - return (ssize_t)(n - nleft); -} - -/* - * Return the current time in microseconds since Epoch - */ -int64_t -hi_usec_now(void) -{ - struct timeval now; - int64_t usec; - int status; - - status = gettimeofday(&now, NULL); - if (status < 0) { - return -1; - } - - usec = (int64_t)now.tv_sec * 1000000LL + (int64_t)now.tv_usec; - - return usec; -} - -/* - * Return the current time in milliseconds since Epoch - */ -int64_t -hi_msec_now(void) -{ - return hi_usec_now() / 1000LL; -} - -void print_string_with_length(char *s, size_t len) -{ - char *token; - for(token = s; token <= s + len; token ++) - { - printf("%c", *token); - } - printf("\n"); -} - -void print_string_with_length_fix_CRLF(char *s, size_t len) -{ - char *token; - for(token = s; token < s + len; token ++) - { - if(*token == CR) - { - printf("\\r"); - } - else if(*token == LF) - { - printf("\\n"); - } - else - { - printf("%c", *token); - } - } - printf("\n"); -} - diff --git a/ext/hiredis-vip-0.3.0/hiutil.h b/ext/hiredis-vip-0.3.0/hiutil.h deleted file mode 100644 index d9e1ddb0b..000000000 --- a/ext/hiredis-vip-0.3.0/hiutil.h +++ /dev/null @@ -1,265 +0,0 @@ -#ifndef __HIUTIL_H_ -#define __HIUTIL_H_ - -#include -#include -#include - -#define HI_OK 0 -#define HI_ERROR -1 -#define HI_EAGAIN -2 -#define HI_ENOMEM -3 - -typedef int rstatus_t; /* return type */ - -#define LF (uint8_t) 10 -#define CR (uint8_t) 13 -#define CRLF "\x0d\x0a" -#define CRLF_LEN (sizeof("\x0d\x0a") - 1) - -#define NELEMS(a) ((sizeof(a)) / sizeof((a)[0])) - -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) - -#define SQUARE(d) ((d) * (d)) -#define VAR(s, s2, n) (((n) < 2) ? 0.0 : ((s2) - SQUARE(s)/(n)) / ((n) - 1)) -#define STDDEV(s, s2, n) (((n) < 2) ? 0.0 : sqrt(VAR((s), (s2), (n)))) - -#define HI_INET4_ADDRSTRLEN (sizeof("255.255.255.255") - 1) -#define HI_INET6_ADDRSTRLEN \ - (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1) -#define HI_INET_ADDRSTRLEN MAX(HI_INET4_ADDRSTRLEN, HI_INET6_ADDRSTRLEN) -#define HI_UNIX_ADDRSTRLEN \ - (sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path)) - -#define HI_MAXHOSTNAMELEN 256 - -/* - * Length of 1 byte, 2 bytes, 4 bytes, 8 bytes and largest integral - * type (uintmax_t) in ascii, including the null terminator '\0' - * - * From stdint.h, we have: - * # define UINT8_MAX (255) - * # define UINT16_MAX (65535) - * # define UINT32_MAX (4294967295U) - * # define UINT64_MAX (__UINT64_C(18446744073709551615)) - */ -#define HI_UINT8_MAXLEN (3 + 1) -#define HI_UINT16_MAXLEN (5 + 1) -#define HI_UINT32_MAXLEN (10 + 1) -#define HI_UINT64_MAXLEN (20 + 1) -#define HI_UINTMAX_MAXLEN HI_UINT64_MAXLEN - -/* - * Make data 'd' or pointer 'p', n-byte aligned, where n is a power of 2 - * of 2. - */ -#define HI_ALIGNMENT sizeof(unsigned long) /* platform word */ -#define HI_ALIGN(d, n) (((d) + (n - 1)) & ~(n - 1)) -#define HI_ALIGN_PTR(p, n) \ - (void *) (((uintptr_t) (p) + ((uintptr_t) n - 1)) & ~((uintptr_t) n - 1)) - - - -#define str3icmp(m, c0, c1, c2) \ - ((m[0] == c0 || m[0] == (c0 ^ 0x20)) && \ - (m[1] == c1 || m[1] == (c1 ^ 0x20)) && \ - (m[2] == c2 || m[2] == (c2 ^ 0x20))) - -#define str4icmp(m, c0, c1, c2, c3) \ - (str3icmp(m, c0, c1, c2) && (m[3] == c3 || m[3] == (c3 ^ 0x20))) - -#define str5icmp(m, c0, c1, c2, c3, c4) \ - (str4icmp(m, c0, c1, c2, c3) && (m[4] == c4 || m[4] == (c4 ^ 0x20))) - -#define str6icmp(m, c0, c1, c2, c3, c4, c5) \ - (str5icmp(m, c0, c1, c2, c3, c4) && (m[5] == c5 || m[5] == (c5 ^ 0x20))) - -#define str7icmp(m, c0, c1, c2, c3, c4, c5, c6) \ - (str6icmp(m, c0, c1, c2, c3, c4, c5) && \ - (m[6] == c6 || m[6] == (c6 ^ 0x20))) - -#define str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ - (str7icmp(m, c0, c1, c2, c3, c4, c5, c6) && \ - (m[7] == c7 || m[7] == (c7 ^ 0x20))) - -#define str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \ - (str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && \ - (m[8] == c8 || m[8] == (c8 ^ 0x20))) - -#define str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) \ - (str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) && \ - (m[9] == c9 || m[9] == (c9 ^ 0x20))) - -#define str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) \ - (str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) && \ - (m[10] == c10 || m[10] == (c10 ^ 0x20))) - -#define str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) \ - (str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) && \ - (m[11] == c11 || m[11] == (c11 ^ 0x20))) - -#define str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) \ - (str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) && \ - (m[12] == c12 || m[12] == (c12 ^ 0x20))) - -#define str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) \ - (str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) && \ - (m[13] == c13 || m[13] == (c13 ^ 0x20))) - -#define str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) \ - (str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) && \ - (m[14] == c14 || m[14] == (c14 ^ 0x20))) - -#define str16icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15) \ - (str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) && \ - (m[15] == c15 || m[15] == (c15 ^ 0x20))) - - - -/* - * Wrapper to workaround well known, safe, implicit type conversion when - * invoking system calls. - */ -#define hi_gethostname(_name, _len) \ - gethostname((char *)_name, (size_t)_len) - -#define hi_atoi(_line, _n) \ - _hi_atoi((uint8_t *)_line, (size_t)_n) -#define hi_itoa(_line, _n) \ - _hi_itoa((uint8_t *)_line, (int)_n) - -#define uint_len(_n) \ - _uint_len((uint32_t)_n) - - -int hi_set_blocking(int sd); -int hi_set_nonblocking(int sd); -int hi_set_reuseaddr(int sd); -int hi_set_tcpnodelay(int sd); -int hi_set_linger(int sd, int timeout); -int hi_set_sndbuf(int sd, int size); -int hi_set_rcvbuf(int sd, int size); -int hi_get_soerror(int sd); -int hi_get_sndbuf(int sd); -int hi_get_rcvbuf(int sd); - -int _hi_atoi(uint8_t *line, size_t n); -void _hi_itoa(uint8_t *s, int num); - -int hi_valid_port(int n); - -int _uint_len(uint32_t num); - - -/* - * Memory allocation and free wrappers. - * - * These wrappers enables us to loosely detect double free, dangling - * pointer access and zero-byte alloc. - */ -#define hi_alloc(_s) \ - _hi_alloc((size_t)(_s), __FILE__, __LINE__) - -#define hi_zalloc(_s) \ - _hi_zalloc((size_t)(_s), __FILE__, __LINE__) - -#define hi_calloc(_n, _s) \ - _hi_calloc((size_t)(_n), (size_t)(_s), __FILE__, __LINE__) - -#define hi_realloc(_p, _s) \ - _hi_realloc(_p, (size_t)(_s), __FILE__, __LINE__) - -#define hi_free(_p) do { \ - _hi_free(_p, __FILE__, __LINE__); \ - (_p) = NULL; \ -} while (0) - -void *_hi_alloc(size_t size, const char *name, int line); -void *_hi_zalloc(size_t size, const char *name, int line); -void *_hi_calloc(size_t nmemb, size_t size, const char *name, int line); -void *_hi_realloc(void *ptr, size_t size, const char *name, int line); -void _hi_free(void *ptr, const char *name, int line); - - -#define hi_strndup(_s, _n) \ - strndup((char *)(_s), (size_t)(_n)); - -/* - * Wrappers to send or receive n byte message on a blocking - * socket descriptor. - */ -#define hi_sendn(_s, _b, _n) \ - _hi_sendn(_s, _b, (size_t)(_n)) - -#define hi_recvn(_s, _b, _n) \ - _hi_recvn(_s, _b, (size_t)(_n)) - -/* - * Wrappers to read or write data to/from (multiple) buffers - * to a file or socket descriptor. - */ -#define hi_read(_d, _b, _n) \ - read(_d, _b, (size_t)(_n)) - -#define hi_readv(_d, _b, _n) \ - readv(_d, _b, (int)(_n)) - -#define hi_write(_d, _b, _n) \ - write(_d, _b, (size_t)(_n)) - -#define hi_writev(_d, _b, _n) \ - writev(_d, _b, (int)(_n)) - -ssize_t _hi_sendn(int sd, const void *vptr, size_t n); -ssize_t _hi_recvn(int sd, void *vptr, size_t n); - -/* - * Wrappers for defining custom assert based on whether macro - * HI_ASSERT_PANIC or HI_ASSERT_LOG was defined at the moment - * ASSERT was called. - */ -#ifdef HI_ASSERT_PANIC - -#define ASSERT(_x) do { \ - if (!(_x)) { \ - hi_assert(#_x, __FILE__, __LINE__, 1); \ - } \ -} while (0) - -#define NOT_REACHED() ASSERT(0) - -#elif HI_ASSERT_LOG - -#define ASSERT(_x) do { \ - if (!(_x)) { \ - hi_assert(#_x, __FILE__, __LINE__, 0); \ - } \ -} while (0) - -#define NOT_REACHED() ASSERT(0) - -#else - -#define ASSERT(_x) - -#define NOT_REACHED() - -#endif - -void hi_assert(const char *cond, const char *file, int line, int panic); -void hi_stacktrace(int skip_count); -void hi_stacktrace_fd(int fd); - -int _scnprintf(char *buf, size_t size, const char *fmt, ...); -int _vscnprintf(char *buf, size_t size, const char *fmt, va_list args); -int64_t hi_usec_now(void); -int64_t hi_msec_now(void); - -void print_string_with_length(char *s, size_t len); -void print_string_with_length_fix_CRLF(char *s, size_t len); - -uint16_t crc16(const char *buf, int len); - -#endif diff --git a/ext/hiredis-vip-0.3.0/net.c b/ext/hiredis-vip-0.3.0/net.c deleted file mode 100644 index 60a2dc754..000000000 --- a/ext/hiredis-vip-0.3.0/net.c +++ /dev/null @@ -1,458 +0,0 @@ -/* Extracted from anet.c to work properly with Hiredis error reporting. - * - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2014, Pieter Noordhuis - * Copyright (c) 2015, Matt Stancliff , - * Jan-Erik Rediger - * - * 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 "fmacros.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "net.h" -#include "sds.h" - -/* Defined in hiredis.c */ -void __redisSetError(redisContext *c, int type, const char *str); - -static void redisContextCloseFd(redisContext *c) { - if (c && c->fd >= 0) { - close(c->fd); - c->fd = -1; - } -} - -static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { - char buf[128] = { 0 }; - size_t len = 0; - - if (prefix != NULL) - len = snprintf(buf,sizeof(buf),"%s: ",prefix); - __redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len); - __redisSetError(c,type,buf); -} - -static int redisSetReuseAddr(redisContext *c) { - int on = 1; - if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - redisContextCloseFd(c); - return REDIS_ERR; - } - return REDIS_OK; -} - -static int redisCreateSocket(redisContext *c, int type) { - int s; - if ((s = socket(type, SOCK_STREAM, 0)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } - c->fd = s; - if (type == AF_INET) { - if (redisSetReuseAddr(c) == REDIS_ERR) { - return REDIS_ERR; - } - } - return REDIS_OK; -} - -static int redisSetBlocking(redisContext *c, int blocking) { - int flags; - - /* Set the socket nonblocking. - * Note that fcntl(2) for F_GETFL and F_SETFL can't be - * interrupted by a signal. */ - if ((flags = fcntl(c->fd, F_GETFL)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); - redisContextCloseFd(c); - return REDIS_ERR; - } - - if (blocking) - flags &= ~O_NONBLOCK; - else - flags |= O_NONBLOCK; - - if (fcntl(c->fd, F_SETFL, flags) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); - redisContextCloseFd(c); - return REDIS_ERR; - } - return REDIS_OK; -} - -int redisKeepAlive(redisContext *c, int interval) { - int val = 1; - int fd = c->fd; - - if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } - - val = interval; - -#ifdef _OSX - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } -#else -#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) - val = interval; - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } - - val = interval/3; - if (val == 0) val = 1; - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } - - val = 3; - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } -#endif -#endif - - return REDIS_OK; -} - -static int redisSetTcpNoDelay(redisContext *c) { - int yes = 1; - if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); - redisContextCloseFd(c); - return REDIS_ERR; - } - return REDIS_OK; -} - -#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) - -static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) { - struct pollfd wfd[1]; - long msec; - - msec = -1; - wfd[0].fd = c->fd; - wfd[0].events = POLLOUT; - - /* Only use timeout when not NULL. */ - if (timeout != NULL) { - if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); - redisContextCloseFd(c); - return REDIS_ERR; - } - - msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); - - if (msec < 0 || msec > INT_MAX) { - msec = INT_MAX; - } - } - - if (errno == EINPROGRESS) { - int res; - - if ((res = poll(wfd, 1, msec)) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); - redisContextCloseFd(c); - return REDIS_ERR; - } else if (res == 0) { - errno = ETIMEDOUT; - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - redisContextCloseFd(c); - return REDIS_ERR; - } - - if (redisCheckSocketError(c) != REDIS_OK) - return REDIS_ERR; - - return REDIS_OK; - } - - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - redisContextCloseFd(c); - return REDIS_ERR; -} - -int redisCheckSocketError(redisContext *c) { - int err = 0; - socklen_t errlen = sizeof(err); - - if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); - return REDIS_ERR; - } - - if (err) { - errno = err; - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } - - return REDIS_OK; -} - -int redisContextSetTimeout(redisContext *c, const struct timeval tv) { - if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); - return REDIS_ERR; - } - if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); - return REDIS_ERR; - } - return REDIS_OK; -} - -static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, - const struct timeval *timeout, - const char *source_addr) { - int s, rv, n; - char _port[6]; /* strlen("65535"); */ - struct addrinfo hints, *servinfo, *bservinfo, *p, *b; - int blocking = (c->flags & REDIS_BLOCK); - int reuseaddr = (c->flags & REDIS_REUSEADDR); - int reuses = 0; - - c->connection_type = REDIS_CONN_TCP; - c->tcp.port = port; - - /* We need to take possession of the passed parameters - * to make them reusable for a reconnect. - * We also carefully check we don't free data we already own, - * as in the case of the reconnect method. - * - * This is a bit ugly, but atleast it works and doesn't leak memory. - **/ - if (c->tcp.host != addr) { - if (c->tcp.host) - free(c->tcp.host); - - c->tcp.host = strdup(addr); - } - - if (timeout) { - if (c->timeout != timeout) { - if (c->timeout == NULL) - c->timeout = malloc(sizeof(struct timeval)); - - memcpy(c->timeout, timeout, sizeof(struct timeval)); - } - } else { - if (c->timeout) - free(c->timeout); - c->timeout = NULL; - } - - if (source_addr == NULL) { - free(c->tcp.source_addr); - c->tcp.source_addr = NULL; - } else if (c->tcp.source_addr != source_addr) { - free(c->tcp.source_addr); - c->tcp.source_addr = strdup(source_addr); - } - - snprintf(_port, 6, "%d", port); - memset(&hints,0,sizeof(hints)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - - /* Try with IPv6 if no IPv4 address was found. We do it in this order since - * in a Redis client you can't afford to test if you have IPv6 connectivity - * as this would add latency to every connect. Otherwise a more sensible - * route could be: Use IPv6 if both addresses are available and there is IPv6 - * connectivity. */ - if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) { - hints.ai_family = AF_INET6; - if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { - __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); - return REDIS_ERR; - } - } - for (p = servinfo; p != NULL; p = p->ai_next) { -addrretry: - if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) - continue; - - c->fd = s; - if (redisSetBlocking(c,0) != REDIS_OK) - goto error; - if (c->tcp.source_addr) { - int bound = 0; - /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ - if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { - char buf[128]; - snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); - __redisSetError(c,REDIS_ERR_OTHER,buf); - goto error; - } - - if (reuseaddr) { - n = 1; - if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, - sizeof(n)) < 0) { - goto error; - } - } - - for (b = bservinfo; b != NULL; b = b->ai_next) { - if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { - bound = 1; - break; - } - } - freeaddrinfo(bservinfo); - if (!bound) { - char buf[128]; - snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); - __redisSetError(c,REDIS_ERR_OTHER,buf); - goto error; - } - } - if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { - if (errno == EHOSTUNREACH) { - redisContextCloseFd(c); - continue; - } else if (errno == EINPROGRESS && !blocking) { - /* This is ok. */ - } else if (errno == EADDRNOTAVAIL && reuseaddr) { - if (++reuses >= REDIS_CONNECT_RETRIES) { - goto error; - } else { - goto addrretry; - } - } else { - if (redisContextWaitReady(c,c->timeout) != REDIS_OK) - goto error; - } - } - if (blocking && redisSetBlocking(c,1) != REDIS_OK) - goto error; - if (redisSetTcpNoDelay(c) != REDIS_OK) - goto error; - - c->flags |= REDIS_CONNECTED; - rv = REDIS_OK; - goto end; - } - if (p == NULL) { - char buf[128]; - snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); - __redisSetError(c,REDIS_ERR_OTHER,buf); - goto error; - } - -error: - rv = REDIS_ERR; -end: - freeaddrinfo(servinfo); - return rv; // Need to return REDIS_OK if alright -} - -int redisContextConnectTcp(redisContext *c, const char *addr, int port, - const struct timeval *timeout) { - return _redisContextConnectTcp(c, addr, port, timeout, NULL); -} - -int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, - const struct timeval *timeout, - const char *source_addr) { - return _redisContextConnectTcp(c, addr, port, timeout, source_addr); -} - -int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { - int blocking = (c->flags & REDIS_BLOCK); - struct sockaddr_un sa; - - if (redisCreateSocket(c,AF_LOCAL) < 0) - return REDIS_ERR; - if (redisSetBlocking(c,0) != REDIS_OK) - return REDIS_ERR; - - c->connection_type = REDIS_CONN_UNIX; - if (c->unix_sock.path != path) - c->unix_sock.path = strdup(path); - - if (timeout) { - if (c->timeout != timeout) { - if (c->timeout == NULL) - c->timeout = malloc(sizeof(struct timeval)); - - memcpy(c->timeout, timeout, sizeof(struct timeval)); - } - } else { - if (c->timeout) - free(c->timeout); - c->timeout = NULL; - } - - sa.sun_family = AF_LOCAL; - strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); - if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { - if (errno == EINPROGRESS && !blocking) { - /* This is ok. */ - } else { - if (redisContextWaitReady(c,c->timeout) != REDIS_OK) - return REDIS_ERR; - } - } - - /* Reset socket to be blocking after connect(2). */ - if (blocking && redisSetBlocking(c,1) != REDIS_OK) - return REDIS_ERR; - - c->flags |= REDIS_CONNECTED; - return REDIS_OK; -} diff --git a/ext/hiredis-vip-0.3.0/read.c b/ext/hiredis-vip-0.3.0/read.c deleted file mode 100644 index df1a467a9..000000000 --- a/ext/hiredis-vip-0.3.0/read.c +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * 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 "fmacros.h" -#include -#include -#ifndef _MSC_VER -#include -#endif -#include -#include -#include - -#include "read.h" -#include "sds.h" - -static void __redisReaderSetError(redisReader *r, int type, const char *str) { - size_t len; - - if (r->reply != NULL && r->fn && r->fn->freeObject) { - r->fn->freeObject(r->reply); - r->reply = NULL; - } - - /* Clear input buffer on errors. */ - if (r->buf != NULL) { - sdsfree(r->buf); - r->buf = NULL; - r->pos = r->len = 0; - } - - /* Reset task stack. */ - r->ridx = -1; - - /* Set error. */ - r->err = type; - len = strlen(str); - len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); - memcpy(r->errstr,str,len); - r->errstr[len] = '\0'; -} - -static size_t chrtos(char *buf, size_t size, char byte) { - size_t len = 0; - - switch(byte) { - case '\\': - case '"': - len = snprintf(buf,size,"\"\\%c\"",byte); - break; - case '\n': len = snprintf(buf,size,"\"\\n\""); break; - case '\r': len = snprintf(buf,size,"\"\\r\""); break; - case '\t': len = snprintf(buf,size,"\"\\t\""); break; - case '\a': len = snprintf(buf,size,"\"\\a\""); break; - case '\b': len = snprintf(buf,size,"\"\\b\""); break; - default: - if (isprint(byte)) - len = snprintf(buf,size,"\"%c\"",byte); - else - len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); - break; - } - - return len; -} - -static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) { - char cbuf[8], sbuf[128]; - - chrtos(cbuf,sizeof(cbuf),byte); - snprintf(sbuf,sizeof(sbuf), - "Protocol error, got %s as reply type byte", cbuf); - __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf); -} - -static void __redisReaderSetErrorOOM(redisReader *r) { - __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory"); -} - -static char *readBytes(redisReader *r, unsigned int bytes) { - char *p; - if (r->len-r->pos >= bytes) { - p = r->buf+r->pos; - r->pos += bytes; - return p; - } - return NULL; -} - -/* Find pointer to \r\n. */ -static char *seekNewline(char *s, size_t len) { - int pos = 0; - int _len = len-1; - - /* Position should be < len-1 because the character at "pos" should be - * followed by a \n. Note that strchr cannot be used because it doesn't - * allow to search a limited length and the buffer that is being searched - * might not have a trailing NULL character. */ - while (pos < _len) { - while(pos < _len && s[pos] != '\r') pos++; - if (s[pos] != '\r') { - /* Not found. */ - return NULL; - } else { - if (s[pos+1] == '\n') { - /* Found. */ - return s+pos; - } else { - /* Continue searching. */ - pos++; - } - } - } - return NULL; -} - -/* Read a long long value starting at *s, under the assumption that it will be - * terminated by \r\n. Ambiguously returns -1 for unexpected input. */ -static long long readLongLong(char *s) { - long long v = 0; - int dec, mult = 1; - char c; - - if (*s == '-') { - mult = -1; - s++; - } else if (*s == '+') { - mult = 1; - s++; - } - - while ((c = *(s++)) != '\r') { - dec = c - '0'; - if (dec >= 0 && dec < 10) { - v *= 10; - v += dec; - } else { - /* Should not happen... */ - return -1; - } - } - - return mult*v; -} - -static char *readLine(redisReader *r, int *_len) { - char *p, *s; - int len; - - p = r->buf+r->pos; - s = seekNewline(p,(r->len-r->pos)); - if (s != NULL) { - len = s-(r->buf+r->pos); - r->pos += len+2; /* skip \r\n */ - if (_len) *_len = len; - return p; - } - return NULL; -} - -static void moveToNextTask(redisReader *r) { - redisReadTask *cur, *prv; - while (r->ridx >= 0) { - /* Return a.s.a.p. when the stack is now empty. */ - if (r->ridx == 0) { - r->ridx--; - return; - } - - cur = &(r->rstack[r->ridx]); - prv = &(r->rstack[r->ridx-1]); - assert(prv->type == REDIS_REPLY_ARRAY); - if (cur->idx == prv->elements-1) { - r->ridx--; - } else { - /* Reset the type because the next item can be anything */ - assert(cur->idx < prv->elements); - cur->type = -1; - cur->elements = -1; - cur->idx++; - return; - } - } -} - -static int processLineItem(redisReader *r) { - redisReadTask *cur = &(r->rstack[r->ridx]); - void *obj; - char *p; - int len; - - if ((p = readLine(r,&len)) != NULL) { - if (cur->type == REDIS_REPLY_INTEGER) { - if (r->fn && r->fn->createInteger) - obj = r->fn->createInteger(cur,readLongLong(p)); - else - obj = (void*)REDIS_REPLY_INTEGER; - } else { - /* Type will be error or status. */ - if (r->fn && r->fn->createString) - obj = r->fn->createString(cur,p,len); - else - obj = (void*)(size_t)(cur->type); - } - - if (obj == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - /* Set reply if this is the root object. */ - if (r->ridx == 0) r->reply = obj; - moveToNextTask(r); - return REDIS_OK; - } - - return REDIS_ERR; -} - -static int processBulkItem(redisReader *r) { - redisReadTask *cur = &(r->rstack[r->ridx]); - void *obj = NULL; - char *p, *s; - long len; - unsigned long bytelen; - int success = 0; - - p = r->buf+r->pos; - s = seekNewline(p,r->len-r->pos); - if (s != NULL) { - p = r->buf+r->pos; - bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ - len = readLongLong(p); - - if (len < 0) { - /* The nil object can always be created. */ - if (r->fn && r->fn->createNil) - obj = r->fn->createNil(cur); - else - obj = (void*)REDIS_REPLY_NIL; - success = 1; - } else { - /* Only continue when the buffer contains the entire bulk item. */ - bytelen += len+2; /* include \r\n */ - if (r->pos+bytelen <= r->len) { - if (r->fn && r->fn->createString) - obj = r->fn->createString(cur,s+2,len); - else - obj = (void*)REDIS_REPLY_STRING; - success = 1; - } - } - - /* Proceed when obj was created. */ - if (success) { - if (obj == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - r->pos += bytelen; - - /* Set reply if this is the root object. */ - if (r->ridx == 0) r->reply = obj; - moveToNextTask(r); - return REDIS_OK; - } - } - - return REDIS_ERR; -} - -static int processMultiBulkItem(redisReader *r) { - redisReadTask *cur = &(r->rstack[r->ridx]); - void *obj; - char *p; - long elements; - int root = 0; - - /* Set error for nested multi bulks with depth > 7 */ - if (r->ridx == 8) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "No support for nested multi bulk replies with depth > 7"); - return REDIS_ERR; - } - - if ((p = readLine(r,NULL)) != NULL) { - elements = readLongLong(p); - root = (r->ridx == 0); - - if (elements == -1) { - if (r->fn && r->fn->createNil) - obj = r->fn->createNil(cur); - else - obj = (void*)REDIS_REPLY_NIL; - - if (obj == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - moveToNextTask(r); - } else { - if (r->fn && r->fn->createArray) - obj = r->fn->createArray(cur,elements); - else - obj = (void*)REDIS_REPLY_ARRAY; - - if (obj == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - /* Modify task stack when there are more than 0 elements. */ - if (elements > 0) { - cur->elements = elements; - cur->obj = obj; - r->ridx++; - r->rstack[r->ridx].type = -1; - r->rstack[r->ridx].elements = -1; - r->rstack[r->ridx].idx = 0; - r->rstack[r->ridx].obj = NULL; - r->rstack[r->ridx].parent = cur; - r->rstack[r->ridx].privdata = r->privdata; - } else { - moveToNextTask(r); - } - } - - /* Set reply if this is the root object. */ - if (root) r->reply = obj; - return REDIS_OK; - } - - return REDIS_ERR; -} - -static int processItem(redisReader *r) { - redisReadTask *cur = &(r->rstack[r->ridx]); - char *p; - - /* check if we need to read type */ - if (cur->type < 0) { - if ((p = readBytes(r,1)) != NULL) { - switch (p[0]) { - case '-': - cur->type = REDIS_REPLY_ERROR; - break; - case '+': - cur->type = REDIS_REPLY_STATUS; - break; - case ':': - cur->type = REDIS_REPLY_INTEGER; - break; - case '$': - cur->type = REDIS_REPLY_STRING; - break; - case '*': - cur->type = REDIS_REPLY_ARRAY; - break; - default: - __redisReaderSetErrorProtocolByte(r,*p); - return REDIS_ERR; - } - } else { - /* could not consume 1 byte */ - return REDIS_ERR; - } - } - - /* process typed item */ - switch(cur->type) { - case REDIS_REPLY_ERROR: - case REDIS_REPLY_STATUS: - case REDIS_REPLY_INTEGER: - return processLineItem(r); - case REDIS_REPLY_STRING: - return processBulkItem(r); - case REDIS_REPLY_ARRAY: - return processMultiBulkItem(r); - default: - assert(NULL); - return REDIS_ERR; /* Avoid warning. */ - } -} - -redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { - redisReader *r; - - r = calloc(sizeof(redisReader),1); - if (r == NULL) - return NULL; - - r->err = 0; - r->errstr[0] = '\0'; - r->fn = fn; - r->buf = sdsempty(); - r->maxbuf = REDIS_READER_MAX_BUF; - if (r->buf == NULL) { - free(r); - return NULL; - } - - r->ridx = -1; - return r; -} - -void redisReaderFree(redisReader *r) { - if (r->reply != NULL && r->fn && r->fn->freeObject) - r->fn->freeObject(r->reply); - if (r->buf != NULL) - sdsfree(r->buf); - free(r); -} - -int redisReaderFeed(redisReader *r, const char *buf, size_t len) { - sds newbuf; - - /* Return early when this reader is in an erroneous state. */ - if (r->err) - return REDIS_ERR; - - /* Copy the provided buffer. */ - if (buf != NULL && len >= 1) { - /* Destroy internal buffer when it is empty and is quite large. */ - if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { - sdsfree(r->buf); - r->buf = sdsempty(); - r->pos = 0; - - /* r->buf should not be NULL since we just free'd a larger one. */ - assert(r->buf != NULL); - } - - newbuf = sdscatlen(r->buf,buf,len); - if (newbuf == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - r->buf = newbuf; - r->len = sdslen(r->buf); - } - - return REDIS_OK; -} - -int redisReaderGetReply(redisReader *r, void **reply) { - /* Default target pointer to NULL. */ - if (reply != NULL) - *reply = NULL; - - /* Return early when this reader is in an erroneous state. */ - if (r->err) - return REDIS_ERR; - - /* When the buffer is empty, there will never be a reply. */ - if (r->len == 0) - return REDIS_OK; - - /* Set first item to process when the stack is empty. */ - if (r->ridx == -1) { - r->rstack[0].type = -1; - r->rstack[0].elements = -1; - r->rstack[0].idx = -1; - r->rstack[0].obj = NULL; - r->rstack[0].parent = NULL; - r->rstack[0].privdata = r->privdata; - r->ridx = 0; - } - - /* Process items in reply. */ - while (r->ridx >= 0) - if (processItem(r) != REDIS_OK) - break; - - /* Return ASAP when an error occurred. */ - if (r->err) - return REDIS_ERR; - - /* Discard part of the buffer when we've consumed at least 1k, to avoid - * doing unnecessary calls to memmove() in sds.c. */ - if (r->pos >= 1024) { - sdsrange(r->buf,r->pos,-1); - r->pos = 0; - r->len = sdslen(r->buf); - } - - /* Emit a reply when there is one. */ - if (r->ridx == -1) { - if (reply != NULL) - *reply = r->reply; - r->reply = NULL; - } - return REDIS_OK; -} diff --git a/ext/hiredis-vip-0.3.0/sds.c b/ext/hiredis-vip-0.3.0/sds.c deleted file mode 100644 index 5d55b7792..000000000 --- a/ext/hiredis-vip-0.3.0/sds.c +++ /dev/null @@ -1,1095 +0,0 @@ -/* SDS (Simple Dynamic Strings), A C dynamic strings library. - * - * Copyright (c) 2006-2014, Salvatore Sanfilippo - * 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 -#include -#include -#include -#include - -#include "sds.h" - -/* Create a new sds string with the content specified by the 'init' pointer - * and 'initlen'. - * If NULL is used for 'init' the string is initialized with zero bytes. - * - * The string is always null-termined (all the sds strings are, always) so - * even if you create an sds string with: - * - * mystring = sdsnewlen("abc",3"); - * - * You can print the string with printf() as there is an implicit \0 at the - * end of the string. However the string is binary safe and can contain - * \0 characters in the middle, as the length is stored in the sds header. */ -sds sdsnewlen(const void *init, size_t initlen) { - struct sdshdr *sh; - - if (init) { - sh = malloc(sizeof *sh+initlen+1); - } else { - sh = calloc(sizeof *sh+initlen+1,1); - } - if (sh == NULL) return NULL; - sh->len = initlen; - sh->free = 0; - if (initlen && init) - memcpy(sh->buf, init, initlen); - sh->buf[initlen] = '\0'; - return (char*)sh->buf; -} - -/* Create an empty (zero length) sds string. Even in this case the string - * always has an implicit null term. */ -sds sdsempty(void) { - return sdsnewlen("",0); -} - -/* Create a new sds string starting from a null termined C string. */ -sds sdsnew(const char *init) { - size_t initlen = (init == NULL) ? 0 : strlen(init); - return sdsnewlen(init, initlen); -} - -/* Duplicate an sds string. */ -sds sdsdup(const sds s) { - return sdsnewlen(s, sdslen(s)); -} - -/* Free an sds string. No operation is performed if 's' is NULL. */ -void sdsfree(sds s) { - if (s == NULL) return; - free(s-sizeof(struct sdshdr)); -} - -/* Set the sds string length to the length as obtained with strlen(), so - * considering as content only up to the first null term character. - * - * This function is useful when the sds string is hacked manually in some - * way, like in the following example: - * - * s = sdsnew("foobar"); - * s[2] = '\0'; - * sdsupdatelen(s); - * printf("%d\n", sdslen(s)); - * - * The output will be "2", but if we comment out the call to sdsupdatelen() - * the output will be "6" as the string was modified but the logical length - * remains 6 bytes. */ -void sdsupdatelen(sds s) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - int reallen = strlen(s); - sh->free += (sh->len-reallen); - sh->len = reallen; -} - -/* Modify an sds string on-place to make it empty (zero length). - * However all the existing buffer is not discarded but set as free space - * so that next append operations will not require allocations up to the - * number of bytes previously available. */ -void sdsclear(sds s) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - sh->free += sh->len; - sh->len = 0; - sh->buf[0] = '\0'; -} - -/* Enlarge the free space at the end of the sds string so that the caller - * is sure that after calling this function can overwrite up to addlen - * bytes after the end of the string, plus one more byte for nul term. - * - * Note: this does not change the *length* of the sds string as returned - * by sdslen(), but only the free buffer space we have. */ -sds sdsMakeRoomFor(sds s, size_t addlen) { - struct sdshdr *sh, *newsh; - size_t free = sdsavail(s); - size_t len, newlen; - - if (free >= addlen) return s; - len = sdslen(s); - sh = (void*) (s-sizeof *sh); - newlen = (len+addlen); - if (newlen < SDS_MAX_PREALLOC) - newlen *= 2; - else - newlen += SDS_MAX_PREALLOC; - newsh = realloc(sh, sizeof *newsh+newlen+1); - if (newsh == NULL) return NULL; - - newsh->free = newlen - len; - return newsh->buf; -} - -/* Reallocate the sds string so that it has no free space at the end. The - * contained string remains not altered, but next concatenation operations - * will require a reallocation. - * - * After the call, the passed sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -sds sdsRemoveFreeSpace(sds s) { - struct sdshdr *sh; - - sh = (void*) (s-sizeof *sh); - sh = realloc(sh, sizeof *sh+sh->len+1); - sh->free = 0; - return sh->buf; -} - -/* Return the total size of the allocation of the specifed sds string, - * including: - * 1) The sds header before the pointer. - * 2) The string. - * 3) The free buffer at the end if any. - * 4) The implicit null term. - */ -size_t sdsAllocSize(sds s) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - - return sizeof(*sh)+sh->len+sh->free+1; -} - -/* Increment the sds length and decrements the left free space at the - * end of the string according to 'incr'. Also set the null term - * in the new end of the string. - * - * This function is used in order to fix the string length after the - * user calls sdsMakeRoomFor(), writes something after the end of - * the current string, and finally needs to set the new length. - * - * Note: it is possible to use a negative increment in order to - * right-trim the string. - * - * Usage example: - * - * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the - * following schema, to cat bytes coming from the kernel to the end of an - * sds string without copying into an intermediate buffer: - * - * oldlen = sdslen(s); - * s = sdsMakeRoomFor(s, BUFFER_SIZE); - * nread = read(fd, s+oldlen, BUFFER_SIZE); - * ... check for nread <= 0 and handle it ... - * sdsIncrLen(s, nread); - */ -void sdsIncrLen(sds s, int incr) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - - assert(sh->free >= incr); - sh->len += incr; - sh->free -= incr; - assert(sh->free >= 0); - s[sh->len] = '\0'; -} - -/* Grow the sds to have the specified length. Bytes that were not part of - * the original length of the sds will be set to zero. - * - * if the specified length is smaller than the current length, no operation - * is performed. */ -sds sdsgrowzero(sds s, size_t len) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - size_t totlen, curlen = sh->len; - - if (len <= curlen) return s; - s = sdsMakeRoomFor(s,len-curlen); - if (s == NULL) return NULL; - - /* Make sure added region doesn't contain garbage */ - sh = (void*)(s-sizeof *sh); - memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ - totlen = sh->len+sh->free; - sh->len = len; - sh->free = totlen-sh->len; - return s; -} - -/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the - * end of the specified sds string 's'. - * - * After the call, the passed sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -sds sdscatlen(sds s, const void *t, size_t len) { - struct sdshdr *sh; - size_t curlen = sdslen(s); - - s = sdsMakeRoomFor(s,len); - if (s == NULL) return NULL; - sh = (void*) (s-sizeof *sh); - memcpy(s+curlen, t, len); - sh->len = curlen+len; - sh->free = sh->free-len; - s[curlen+len] = '\0'; - return s; -} - -/* Append the specified null termianted C string to the sds string 's'. - * - * After the call, the passed sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -sds sdscat(sds s, const char *t) { - return sdscatlen(s, t, strlen(t)); -} - -/* Append the specified sds 't' to the existing sds 's'. - * - * After the call, the modified sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -sds sdscatsds(sds s, const sds t) { - return sdscatlen(s, t, sdslen(t)); -} - -/* Destructively modify the sds string 's' to hold the specified binary - * safe string pointed by 't' of length 'len' bytes. */ -sds sdscpylen(sds s, const char *t, size_t len) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - size_t totlen = sh->free+sh->len; - - if (totlen < len) { - s = sdsMakeRoomFor(s,len-sh->len); - if (s == NULL) return NULL; - sh = (void*) (s-sizeof *sh); - totlen = sh->free+sh->len; - } - memcpy(s, t, len); - s[len] = '\0'; - sh->len = len; - sh->free = totlen-len; - return s; -} - -/* Like sdscpylen() but 't' must be a null-termined string so that the length - * of the string is obtained with strlen(). */ -sds sdscpy(sds s, const char *t) { - return sdscpylen(s, t, strlen(t)); -} - -/* Helper for sdscatlonglong() doing the actual number -> string - * conversion. 's' must point to a string with room for at least - * SDS_LLSTR_SIZE bytes. - * - * The function returns the lenght of the null-terminated string - * representation stored at 's'. */ -#define SDS_LLSTR_SIZE 21 -int sdsll2str(char *s, long long value) { - char *p, aux; - unsigned long long v; - size_t l; - - /* Generate the string representation, this method produces - * an reversed string. */ - v = (value < 0) ? -value : value; - p = s; - do { - *p++ = '0'+(v%10); - v /= 10; - } while(v); - if (value < 0) *p++ = '-'; - - /* Compute length and add null term. */ - l = p-s; - *p = '\0'; - - /* Reverse the string. */ - p--; - while(s < p) { - aux = *s; - *s = *p; - *p = aux; - s++; - p--; - } - return l; -} - -/* Identical sdsll2str(), but for unsigned long long type. */ -int sdsull2str(char *s, unsigned long long v) { - char *p, aux; - size_t l; - - /* Generate the string representation, this method produces - * an reversed string. */ - p = s; - do { - *p++ = '0'+(v%10); - v /= 10; - } while(v); - - /* Compute length and add null term. */ - l = p-s; - *p = '\0'; - - /* Reverse the string. */ - p--; - while(s < p) { - aux = *s; - *s = *p; - *p = aux; - s++; - p--; - } - return l; -} - -/* Like sdscatpritf() but gets va_list instead of being variadic. */ -sds sdscatvprintf(sds s, const char *fmt, va_list ap) { - va_list cpy; - char *buf, *t; - size_t buflen = 16; - - while(1) { - buf = malloc(buflen); - if (buf == NULL) return NULL; - buf[buflen-2] = '\0'; - va_copy(cpy,ap); - vsnprintf(buf, buflen, fmt, cpy); - if (buf[buflen-2] != '\0') { - free(buf); - buflen *= 2; - continue; - } - break; - } - t = sdscat(s, buf); - free(buf); - return t; -} - -/* Append to the sds string 's' a string obtained using printf-alike format - * specifier. - * - * After the call, the modified sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. - * - * Example: - * - * s = sdsnew("Sum is: "); - * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b); - * - * Often you need to create a string from scratch with the printf-alike - * format. When this is the need, just use sdsempty() as the target string: - * - * s = sdscatprintf(sdsempty(), "... your format ...", args); - */ -sds sdscatprintf(sds s, const char *fmt, ...) { - va_list ap; - char *t; - va_start(ap, fmt); - t = sdscatvprintf(s,fmt,ap); - va_end(ap); - return t; -} - -/* This function is similar to sdscatprintf, but much faster as it does - * not rely on sprintf() family functions implemented by the libc that - * are often very slow. Moreover directly handling the sds string as - * new data is concatenated provides a performance improvement. - * - * However this function only handles an incompatible subset of printf-alike - * format specifiers: - * - * %s - C String - * %S - SDS string - * %i - signed int - * %I - 64 bit signed integer (long long, int64_t) - * %u - unsigned int - * %U - 64 bit unsigned integer (unsigned long long, uint64_t) - * %T - A size_t variable. - * %% - Verbatim "%" character. - */ -sds sdscatfmt(sds s, char const *fmt, ...) { - struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); - size_t initlen = sdslen(s); - const char *f = fmt; - int i; - va_list ap; - - va_start(ap,fmt); - f = fmt; /* Next format specifier byte to process. */ - i = initlen; /* Position of the next byte to write to dest str. */ - while(*f) { - char next, *str; - int l; - long long num; - unsigned long long unum; - - /* Make sure there is always space for at least 1 char. */ - if (sh->free == 0) { - s = sdsMakeRoomFor(s,1); - sh = (void*) (s-(sizeof(struct sdshdr))); - } - - switch(*f) { - case '%': - next = *(f+1); - f++; - switch(next) { - case 's': - case 'S': - str = va_arg(ap,char*); - l = (next == 's') ? strlen(str) : sdslen(str); - if (sh->free < l) { - s = sdsMakeRoomFor(s,l); - sh = (void*) (s-(sizeof(struct sdshdr))); - } - memcpy(s+i,str,l); - sh->len += l; - sh->free -= l; - i += l; - break; - case 'i': - case 'I': - if (next == 'i') - num = va_arg(ap,int); - else - num = va_arg(ap,long long); - { - char buf[SDS_LLSTR_SIZE]; - l = sdsll2str(buf,num); - if (sh->free < l) { - s = sdsMakeRoomFor(s,l); - sh = (void*) (s-(sizeof(struct sdshdr))); - } - memcpy(s+i,buf,l); - sh->len += l; - sh->free -= l; - i += l; - } - break; - case 'u': - case 'U': - case 'T': - if (next == 'u') - unum = va_arg(ap,unsigned int); - else if(next == 'U') - unum = va_arg(ap,unsigned long long); - else - unum = (unsigned long long)va_arg(ap,size_t); - { - char buf[SDS_LLSTR_SIZE]; - l = sdsull2str(buf,unum); - if (sh->free < l) { - s = sdsMakeRoomFor(s,l); - sh = (void*) (s-(sizeof(struct sdshdr))); - } - memcpy(s+i,buf,l); - sh->len += l; - sh->free -= l; - i += l; - } - break; - default: /* Handle %% and generally %. */ - s[i++] = next; - sh->len += 1; - sh->free -= 1; - break; - } - break; - default: - s[i++] = *f; - sh->len += 1; - sh->free -= 1; - break; - } - f++; - } - va_end(ap); - - /* Add null-term */ - s[i] = '\0'; - return s; -} - - -/* Remove the part of the string from left and from right composed just of - * contiguous characters found in 'cset', that is a null terminted C string. - * - * After the call, the modified sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. - * - * Example: - * - * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); - * s = sdstrim(s,"A. :"); - * printf("%s\n", s); - * - * Output will be just "Hello World". - */ -void sdstrim(sds s, const char *cset) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - char *start, *end, *sp, *ep; - size_t len; - - sp = start = s; - ep = end = s+sdslen(s)-1; - while(sp <= end && strchr(cset, *sp)) sp++; - while(ep > start && strchr(cset, *ep)) ep--; - len = (sp > ep) ? 0 : ((ep-sp)+1); - if (sh->buf != sp) memmove(sh->buf, sp, len); - sh->buf[len] = '\0'; - sh->free = sh->free+(sh->len-len); - sh->len = len; -} - -/* Turn the string into a smaller (or equal) string containing only the - * substring specified by the 'start' and 'end' indexes. - * - * start and end can be negative, where -1 means the last character of the - * string, -2 the penultimate character, and so forth. - * - * The interval is inclusive, so the start and end characters will be part - * of the resulting string. - * - * The string is modified in-place. - * - * Example: - * - * s = sdsnew("Hello World"); - * sdsrange(s,1,-1); => "ello World" - */ -void sdsrange(sds s, int start, int end) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - size_t newlen, len = sdslen(s); - - if (len == 0) return; - if (start < 0) { - start = len+start; - if (start < 0) start = 0; - } - if (end < 0) { - end = len+end; - if (end < 0) end = 0; - } - newlen = (start > end) ? 0 : (end-start)+1; - if (newlen != 0) { - if (start >= (signed)len) { - newlen = 0; - } else if (end >= (signed)len) { - end = len-1; - newlen = (start > end) ? 0 : (end-start)+1; - } - } else { - start = 0; - } - if (start && newlen) memmove(sh->buf, sh->buf+start, newlen); - sh->buf[newlen] = 0; - sh->free = sh->free+(sh->len-newlen); - sh->len = newlen; -} - -/* Apply tolower() to every character of the sds string 's'. */ -void sdstolower(sds s) { - int len = sdslen(s), j; - - for (j = 0; j < len; j++) s[j] = tolower(s[j]); -} - -/* Apply toupper() to every character of the sds string 's'. */ -void sdstoupper(sds s) { - int len = sdslen(s), j; - - for (j = 0; j < len; j++) s[j] = toupper(s[j]); -} - -/* Compare two sds strings s1 and s2 with memcmp(). - * - * Return value: - * - * 1 if s1 > s2. - * -1 if s1 < s2. - * 0 if s1 and s2 are exactly the same binary string. - * - * If two strings share exactly the same prefix, but one of the two has - * additional characters, the longer string is considered to be greater than - * the smaller one. */ -int sdscmp(const sds s1, const sds s2) { - size_t l1, l2, minlen; - int cmp; - - l1 = sdslen(s1); - l2 = sdslen(s2); - minlen = (l1 < l2) ? l1 : l2; - cmp = memcmp(s1,s2,minlen); - if (cmp == 0) return l1-l2; - return cmp; -} - -/* Split 's' with separator in 'sep'. An array - * of sds strings is returned. *count will be set - * by reference to the number of tokens returned. - * - * On out of memory, zero length string, zero length - * separator, NULL is returned. - * - * Note that 'sep' is able to split a string using - * a multi-character separator. For example - * sdssplit("foo_-_bar","_-_"); will return two - * elements "foo" and "bar". - * - * This version of the function is binary-safe but - * requires length arguments. sdssplit() is just the - * same function but for zero-terminated strings. - */ -sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { - int elements = 0, slots = 5, start = 0, j; - sds *tokens; - - if (seplen < 1 || len < 0) return NULL; - - tokens = malloc(sizeof(sds)*slots); - if (tokens == NULL) return NULL; - - if (len == 0) { - *count = 0; - return tokens; - } - for (j = 0; j < (len-(seplen-1)); j++) { - /* make sure there is room for the next element and the final one */ - if (slots < elements+2) { - sds *newtokens; - - slots *= 2; - newtokens = realloc(tokens,sizeof(sds)*slots); - if (newtokens == NULL) goto cleanup; - tokens = newtokens; - } - /* search the separator */ - if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { - tokens[elements] = sdsnewlen(s+start,j-start); - if (tokens[elements] == NULL) goto cleanup; - elements++; - start = j+seplen; - j = j+seplen-1; /* skip the separator */ - } - } - /* Add the final element. We are sure there is room in the tokens array. */ - tokens[elements] = sdsnewlen(s+start,len-start); - if (tokens[elements] == NULL) goto cleanup; - elements++; - *count = elements; - return tokens; - -cleanup: - { - int i; - for (i = 0; i < elements; i++) sdsfree(tokens[i]); - free(tokens); - *count = 0; - return NULL; - } -} - -/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ -void sdsfreesplitres(sds *tokens, int count) { - if (!tokens) return; - while(count--) - sdsfree(tokens[count]); - free(tokens); -} - -/* Create an sds string from a long long value. It is much faster than: - * - * sdscatprintf(sdsempty(),"%lld\n", value); - */ -sds sdsfromlonglong(long long value) { - char buf[32], *p; - unsigned long long v; - - v = (value < 0) ? -value : value; - p = buf+31; /* point to the last character */ - do { - *p-- = '0'+(v%10); - v /= 10; - } while(v); - if (value < 0) *p-- = '-'; - p++; - return sdsnewlen(p,32-(p-buf)); -} - -/* Append to the sds string "s" an escaped string representation where - * all the non-printable characters (tested with isprint()) are turned into - * escapes in the form "\n\r\a...." or "\x". - * - * After the call, the modified sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -sds sdscatrepr(sds s, const char *p, size_t len) { - s = sdscatlen(s,"\"",1); - while(len--) { - switch(*p) { - case '\\': - case '"': - s = sdscatprintf(s,"\\%c",*p); - break; - case '\n': s = sdscatlen(s,"\\n",2); break; - case '\r': s = sdscatlen(s,"\\r",2); break; - case '\t': s = sdscatlen(s,"\\t",2); break; - case '\a': s = sdscatlen(s,"\\a",2); break; - case '\b': s = sdscatlen(s,"\\b",2); break; - default: - if (isprint(*p)) - s = sdscatprintf(s,"%c",*p); - else - s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); - break; - } - p++; - } - return sdscatlen(s,"\"",1); -} - -/* Helper function for sdssplitargs() that returns non zero if 'c' - * is a valid hex digit. */ -int is_hex_digit(char c) { - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || - (c >= 'A' && c <= 'F'); -} - -/* Helper function for sdssplitargs() that converts a hex digit into an - * integer from 0 to 15 */ -int hex_digit_to_int(char c) { - switch(c) { - case '0': return 0; - case '1': return 1; - case '2': return 2; - case '3': return 3; - case '4': return 4; - case '5': return 5; - case '6': return 6; - case '7': return 7; - case '8': return 8; - case '9': return 9; - case 'a': case 'A': return 10; - case 'b': case 'B': return 11; - case 'c': case 'C': return 12; - case 'd': case 'D': return 13; - case 'e': case 'E': return 14; - case 'f': case 'F': return 15; - default: return 0; - } -} - -/* Split a line into arguments, where every argument can be in the - * following programming-language REPL-alike form: - * - * foo bar "newline are supported\n" and "\xff\x00otherstuff" - * - * The number of arguments is stored into *argc, and an array - * of sds is returned. - * - * The caller should free the resulting array of sds strings with - * sdsfreesplitres(). - * - * Note that sdscatrepr() is able to convert back a string into - * a quoted string in the same format sdssplitargs() is able to parse. - * - * The function returns the allocated tokens on success, even when the - * input string is empty, or NULL if the input contains unbalanced - * quotes or closed quotes followed by non space characters - * as in: "foo"bar or "foo' - */ -sds *sdssplitargs(const char *line, int *argc) { - const char *p = line; - char *current = NULL; - char **vector = NULL; - - *argc = 0; - while(1) { - /* skip blanks */ - while(*p && isspace(*p)) p++; - if (*p) { - /* get a token */ - int inq=0; /* set to 1 if we are in "quotes" */ - int insq=0; /* set to 1 if we are in 'single quotes' */ - int done=0; - - if (current == NULL) current = sdsempty(); - while(!done) { - if (inq) { - if (*p == '\\' && *(p+1) == 'x' && - is_hex_digit(*(p+2)) && - is_hex_digit(*(p+3))) - { - unsigned char byte; - - byte = (hex_digit_to_int(*(p+2))*16)+ - hex_digit_to_int(*(p+3)); - current = sdscatlen(current,(char*)&byte,1); - p += 3; - } else if (*p == '\\' && *(p+1)) { - char c; - - p++; - switch(*p) { - case 'n': c = '\n'; break; - case 'r': c = '\r'; break; - case 't': c = '\t'; break; - case 'b': c = '\b'; break; - case 'a': c = '\a'; break; - default: c = *p; break; - } - current = sdscatlen(current,&c,1); - } else if (*p == '"') { - /* closing quote must be followed by a space or - * nothing at all. */ - if (*(p+1) && !isspace(*(p+1))) goto err; - done=1; - } else if (!*p) { - /* unterminated quotes */ - goto err; - } else { - current = sdscatlen(current,p,1); - } - } else if (insq) { - if (*p == '\\' && *(p+1) == '\'') { - p++; - current = sdscatlen(current,"'",1); - } else if (*p == '\'') { - /* closing quote must be followed by a space or - * nothing at all. */ - if (*(p+1) && !isspace(*(p+1))) goto err; - done=1; - } else if (!*p) { - /* unterminated quotes */ - goto err; - } else { - current = sdscatlen(current,p,1); - } - } else { - switch(*p) { - case ' ': - case '\n': - case '\r': - case '\t': - case '\0': - done=1; - break; - case '"': - inq=1; - break; - case '\'': - insq=1; - break; - default: - current = sdscatlen(current,p,1); - break; - } - } - if (*p) p++; - } - /* add the token to the vector */ - vector = realloc(vector,((*argc)+1)*sizeof(char*)); - vector[*argc] = current; - (*argc)++; - current = NULL; - } else { - /* Even on empty input string return something not NULL. */ - if (vector == NULL) vector = malloc(sizeof(void*)); - return vector; - } - } - -err: - while((*argc)--) - sdsfree(vector[*argc]); - free(vector); - if (current) sdsfree(current); - *argc = 0; - return NULL; -} - -/* Modify the string substituting all the occurrences of the set of - * characters specified in the 'from' string to the corresponding character - * in the 'to' array. - * - * For instance: sdsmapchars(mystring, "ho", "01", 2) - * will have the effect of turning the string "hello" into "0ell1". - * - * The function returns the sds string pointer, that is always the same - * as the input pointer since no resize is needed. */ -sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { - size_t j, i, l = sdslen(s); - - for (j = 0; j < l; j++) { - for (i = 0; i < setlen; i++) { - if (s[j] == from[i]) { - s[j] = to[i]; - break; - } - } - } - return s; -} - -/* Join an array of C strings using the specified separator (also a C string). - * Returns the result as an sds string. */ -sds sdsjoin(char **argv, int argc, char *sep, size_t seplen) { - sds join = sdsempty(); - int j; - - for (j = 0; j < argc; j++) { - join = sdscat(join, argv[j]); - if (j != argc-1) join = sdscatlen(join,sep,seplen); - } - return join; -} - -/* Like sdsjoin, but joins an array of SDS strings. */ -sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { - sds join = sdsempty(); - int j; - - for (j = 0; j < argc; j++) { - join = sdscatsds(join, argv[j]); - if (j != argc-1) join = sdscatlen(join,sep,seplen); - } - return join; -} - -#ifdef SDS_TEST_MAIN -#include -#include "testhelp.h" - -int main(void) { - { - struct sdshdr *sh; - sds x = sdsnew("foo"), y; - - test_cond("Create a string and obtain the length", - sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) - - sdsfree(x); - x = sdsnewlen("foo",2); - test_cond("Create a string with specified length", - sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) - - x = sdscat(x,"bar"); - test_cond("Strings concatenation", - sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); - - x = sdscpy(x,"a"); - test_cond("sdscpy() against an originally longer string", - sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) - - x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); - test_cond("sdscpy() against an originally shorter string", - sdslen(x) == 33 && - memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) - - sdsfree(x); - x = sdscatprintf(sdsempty(),"%d",123); - test_cond("sdscatprintf() seems working in the base case", - sdslen(x) == 3 && memcmp(x,"123\0",4) ==0) - - sdsfree(x); - x = sdsnew("xxciaoyyy"); - sdstrim(x,"xy"); - test_cond("sdstrim() correctly trims characters", - sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) - - y = sdsdup(x); - sdsrange(y,1,1); - test_cond("sdsrange(...,1,1)", - sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) - - sdsfree(y); - y = sdsdup(x); - sdsrange(y,1,-1); - test_cond("sdsrange(...,1,-1)", - sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) - - sdsfree(y); - y = sdsdup(x); - sdsrange(y,-2,-1); - test_cond("sdsrange(...,-2,-1)", - sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) - - sdsfree(y); - y = sdsdup(x); - sdsrange(y,2,1); - test_cond("sdsrange(...,2,1)", - sdslen(y) == 0 && memcmp(y,"\0",1) == 0) - - sdsfree(y); - y = sdsdup(x); - sdsrange(y,1,100); - test_cond("sdsrange(...,1,100)", - sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) - - sdsfree(y); - y = sdsdup(x); - sdsrange(y,100,100); - test_cond("sdsrange(...,100,100)", - sdslen(y) == 0 && memcmp(y,"\0",1) == 0) - - sdsfree(y); - sdsfree(x); - x = sdsnew("foo"); - y = sdsnew("foa"); - test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) - - sdsfree(y); - sdsfree(x); - x = sdsnew("bar"); - y = sdsnew("bar"); - test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) - - sdsfree(y); - sdsfree(x); - x = sdsnew("aar"); - y = sdsnew("bar"); - test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) - - sdsfree(y); - sdsfree(x); - x = sdsnewlen("\a\n\0foo\r",7); - y = sdscatrepr(sdsempty(),x,sdslen(x)); - test_cond("sdscatrepr(...data...)", - memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) - - { - int oldfree; - - sdsfree(x); - x = sdsnew("0"); - sh = (void*) (x-(sizeof(struct sdshdr))); - test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0); - x = sdsMakeRoomFor(x,1); - sh = (void*) (x-(sizeof(struct sdshdr))); - test_cond("sdsMakeRoomFor()", sh->len == 1 && sh->free > 0); - oldfree = sh->free; - x[1] = '1'; - sdsIncrLen(x,1); - test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1'); - test_cond("sdsIncrLen() -- len", sh->len == 2); - test_cond("sdsIncrLen() -- free", sh->free == oldfree-1); - } - } - test_report() - return 0; -} -#endif diff --git a/ext/hiredis-vip-0.3.0/sds.h b/ext/hiredis-vip-0.3.0/sds.h deleted file mode 100644 index 19a2abd31..000000000 --- a/ext/hiredis-vip-0.3.0/sds.h +++ /dev/null @@ -1,105 +0,0 @@ -/* SDS (Simple Dynamic Strings), A C dynamic strings library. - * - * Copyright (c) 2006-2014, Salvatore Sanfilippo - * 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. - */ - -#ifndef __SDS_H -#define __SDS_H - -#define SDS_MAX_PREALLOC (1024*1024) - -#include -#include -#ifdef _MSC_VER -#include "win32.h" -#endif - -typedef char *sds; - -struct sdshdr { - int len; - int free; - char buf[]; -}; - -static inline size_t sdslen(const sds s) { - struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh); - return sh->len; -} - -static inline size_t sdsavail(const sds s) { - struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh); - return sh->free; -} - -sds sdsnewlen(const void *init, size_t initlen); -sds sdsnew(const char *init); -sds sdsempty(void); -size_t sdslen(const sds s); -sds sdsdup(const sds s); -void sdsfree(sds s); -size_t sdsavail(const sds s); -sds sdsgrowzero(sds s, size_t len); -sds sdscatlen(sds s, const void *t, size_t len); -sds sdscat(sds s, const char *t); -sds sdscatsds(sds s, const sds t); -sds sdscpylen(sds s, const char *t, size_t len); -sds sdscpy(sds s, const char *t); - -sds sdscatvprintf(sds s, const char *fmt, va_list ap); -#ifdef __GNUC__ -sds sdscatprintf(sds s, const char *fmt, ...) - __attribute__((format(printf, 2, 3))); -#else -sds sdscatprintf(sds s, const char *fmt, ...); -#endif - -sds sdscatfmt(sds s, char const *fmt, ...); -void sdstrim(sds s, const char *cset); -void sdsrange(sds s, int start, int end); -void sdsupdatelen(sds s); -void sdsclear(sds s); -int sdscmp(const sds s1, const sds s2); -sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); -void sdsfreesplitres(sds *tokens, int count); -void sdstolower(sds s); -void sdstoupper(sds s); -sds sdsfromlonglong(long long value); -sds sdscatrepr(sds s, const char *p, size_t len); -sds *sdssplitargs(const char *line, int *argc); -sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); -sds sdsjoin(char **argv, int argc, char *sep, size_t seplen); -sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); - -/* Low level functions exposed to the user API */ -sds sdsMakeRoomFor(sds s, size_t addlen); -void sdsIncrLen(sds s, int incr); -sds sdsRemoveFreeSpace(sds s); -size_t sdsAllocSize(sds s); - -#endif diff --git a/ext/hiredis-vip-0.3.0/test.c b/ext/hiredis-vip-0.3.0/test.c deleted file mode 100644 index 8fde55446..000000000 --- a/ext/hiredis-vip-0.3.0/test.c +++ /dev/null @@ -1,806 +0,0 @@ -#include "fmacros.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "hiredis.h" -#include "net.h" - -enum connection_type { - CONN_TCP, - CONN_UNIX, - CONN_FD -}; - -struct config { - enum connection_type type; - - struct { - const char *host; - int port; - struct timeval timeout; - } tcp; - - struct { - const char *path; - } unix; -}; - -/* The following lines make up our testing "framework" :) */ -static int tests = 0, fails = 0; -#define test(_s) { printf("#%02d ", ++tests); printf(_s); } -#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} - -static long long usec(void) { - struct timeval tv; - gettimeofday(&tv,NULL); - return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; -} - -/* The assert() calls below have side effects, so we need assert() - * even if we are compiling without asserts (-DNDEBUG). */ -#ifdef NDEBUG -#undef assert -#define assert(e) (void)(e) -#endif - -static redisContext *select_database(redisContext *c) { - redisReply *reply; - - /* Switch to DB 9 for testing, now that we know we can chat. */ - reply = redisCommand(c,"SELECT 9"); - assert(reply != NULL); - freeReplyObject(reply); - - /* Make sure the DB is emtpy */ - reply = redisCommand(c,"DBSIZE"); - assert(reply != NULL); - if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) { - /* Awesome, DB 9 is empty and we can continue. */ - freeReplyObject(reply); - } else { - printf("Database #9 is not empty, test can not continue\n"); - exit(1); - } - - return c; -} - -static int disconnect(redisContext *c, int keep_fd) { - redisReply *reply; - - /* Make sure we're on DB 9. */ - reply = redisCommand(c,"SELECT 9"); - assert(reply != NULL); - freeReplyObject(reply); - reply = redisCommand(c,"FLUSHDB"); - assert(reply != NULL); - freeReplyObject(reply); - - /* Free the context as well, but keep the fd if requested. */ - if (keep_fd) - return redisFreeKeepFd(c); - redisFree(c); - return -1; -} - -static redisContext *connect(struct config config) { - redisContext *c = NULL; - - if (config.type == CONN_TCP) { - c = redisConnect(config.tcp.host, config.tcp.port); - } else if (config.type == CONN_UNIX) { - c = redisConnectUnix(config.unix.path); - } else if (config.type == CONN_FD) { - /* Create a dummy connection just to get an fd to inherit */ - redisContext *dummy_ctx = redisConnectUnix(config.unix.path); - if (dummy_ctx) { - int fd = disconnect(dummy_ctx, 1); - printf("Connecting to inherited fd %d\n", fd); - c = redisConnectFd(fd); - } - } else { - assert(NULL); - } - - if (c == NULL) { - printf("Connection error: can't allocate redis context\n"); - exit(1); - } else if (c->err) { - printf("Connection error: %s\n", c->errstr); - redisFree(c); - exit(1); - } - - return select_database(c); -} - -static void test_format_commands(void) { - char *cmd; - int len; - - test("Format command without interpolation: "); - len = redisFormatCommand(&cmd,"SET foo bar"); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(3+2)); - free(cmd); - - test("Format command with %%s string interpolation: "); - len = redisFormatCommand(&cmd,"SET %s %s","foo","bar"); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(3+2)); - free(cmd); - - test("Format command with %%s and an empty string: "); - len = redisFormatCommand(&cmd,"SET %s %s","foo",""); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(0+2)); - free(cmd); - - test("Format command with an empty string in between proper interpolations: "); - len = redisFormatCommand(&cmd,"SET %s %s","","foo"); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 && - len == 4+4+(3+2)+4+(0+2)+4+(3+2)); - free(cmd); - - test("Format command with %%b string interpolation: "); - len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(3+2)); - free(cmd); - - test("Format command with %%b and an empty string: "); - len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(0+2)); - free(cmd); - - test("Format command with literal %%: "); - len = redisFormatCommand(&cmd,"SET %% %%"); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 && - len == 4+4+(3+2)+4+(1+2)+4+(1+2)); - free(cmd); - - /* Vararg width depends on the type. These tests make sure that the - * width is correctly determined using the format and subsequent varargs - * can correctly be interpolated. */ -#define INTEGER_WIDTH_TEST(fmt, type) do { \ - type value = 123; \ - test("Format command with printf-delegation (" #type "): "); \ - len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \ - test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \ - len == 4+5+(12+2)+4+(9+2)); \ - free(cmd); \ -} while(0) - -#define FLOAT_WIDTH_TEST(type) do { \ - type value = 123.0; \ - test("Format command with printf-delegation (" #type "): "); \ - len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \ - test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \ - len == 4+5+(12+2)+4+(9+2)); \ - free(cmd); \ -} while(0) - - INTEGER_WIDTH_TEST("d", int); - INTEGER_WIDTH_TEST("hhd", char); - INTEGER_WIDTH_TEST("hd", short); - INTEGER_WIDTH_TEST("ld", long); - INTEGER_WIDTH_TEST("lld", long long); - INTEGER_WIDTH_TEST("u", unsigned int); - INTEGER_WIDTH_TEST("hhu", unsigned char); - INTEGER_WIDTH_TEST("hu", unsigned short); - INTEGER_WIDTH_TEST("lu", unsigned long); - INTEGER_WIDTH_TEST("llu", unsigned long long); - FLOAT_WIDTH_TEST(float); - FLOAT_WIDTH_TEST(double); - - test("Format command with invalid printf format: "); - len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); - test_cond(len == -1); - - const char *argv[3]; - argv[0] = "SET"; - argv[1] = "foo\0xxx"; - argv[2] = "bar"; - size_t lens[3] = { 3, 7, 3 }; - int argc = 3; - - test("Format command by passing argc/argv without lengths: "); - len = redisFormatCommandArgv(&cmd,argc,argv,NULL); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(3+2)); - free(cmd); - - test("Format command by passing argc/argv with lengths: "); - len = redisFormatCommandArgv(&cmd,argc,argv,lens); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && - len == 4+4+(3+2)+4+(7+2)+4+(3+2)); - free(cmd); -} - -static void test_append_formatted_commands(struct config config) { - redisContext *c; - redisReply *reply; - char *cmd; - int len; - - c = connect(config); - - test("Append format command: "); - - len = redisFormatCommand(&cmd, "SET foo bar"); - - test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK); - - assert(redisGetReply(c, (void*)&reply) == REDIS_OK); - - free(cmd); - freeReplyObject(reply); - - disconnect(c, 0); -} - -static void test_reply_reader(void) { - redisReader *reader; - void *reply; - int ret; - int i; - - test("Error handling in reply parser: "); - reader = redisReaderCreate(); - redisReaderFeed(reader,(char*)"@foo\r\n",6); - ret = redisReaderGetReply(reader,NULL); - test_cond(ret == REDIS_ERR && - strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); - redisReaderFree(reader); - - /* when the reply already contains multiple items, they must be free'd - * on an error. valgrind will bark when this doesn't happen. */ - test("Memory cleanup in reply parser: "); - reader = redisReaderCreate(); - redisReaderFeed(reader,(char*)"*2\r\n",4); - redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); - redisReaderFeed(reader,(char*)"@foo\r\n",6); - ret = redisReaderGetReply(reader,NULL); - test_cond(ret == REDIS_ERR && - strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); - redisReaderFree(reader); - - test("Set error on nested multi bulks with depth > 7: "); - reader = redisReaderCreate(); - - for (i = 0; i < 9; i++) { - redisReaderFeed(reader,(char*)"*1\r\n",4); - } - - ret = redisReaderGetReply(reader,NULL); - test_cond(ret == REDIS_ERR && - strncasecmp(reader->errstr,"No support for",14) == 0); - redisReaderFree(reader); - - test("Works with NULL functions for reply: "); - reader = redisReaderCreate(); - reader->fn = NULL; - redisReaderFeed(reader,(char*)"+OK\r\n",5); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); - redisReaderFree(reader); - - test("Works when a single newline (\\r\\n) covers two calls to feed: "); - reader = redisReaderCreate(); - reader->fn = NULL; - redisReaderFeed(reader,(char*)"+OK\r",4); - ret = redisReaderGetReply(reader,&reply); - assert(ret == REDIS_OK && reply == NULL); - redisReaderFeed(reader,(char*)"\n",1); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); - redisReaderFree(reader); - - test("Don't reset state after protocol error: "); - reader = redisReaderCreate(); - reader->fn = NULL; - redisReaderFeed(reader,(char*)"x",1); - ret = redisReaderGetReply(reader,&reply); - assert(ret == REDIS_ERR); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_ERR && reply == NULL); - redisReaderFree(reader); - - /* Regression test for issue #45 on GitHub. */ - test("Don't do empty allocation for empty multi bulk: "); - reader = redisReaderCreate(); - redisReaderFeed(reader,(char*)"*0\r\n",4); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && - ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && - ((redisReply*)reply)->elements == 0); - freeReplyObject(reply); - redisReaderFree(reader); -} - -static void test_free_null(void) { - void *redisContext = NULL; - void *reply = NULL; - - test("Don't fail when redisFree is passed a NULL value: "); - redisFree(redisContext); - test_cond(redisContext == NULL); - - test("Don't fail when freeReplyObject is passed a NULL value: "); - freeReplyObject(reply); - test_cond(reply == NULL); -} - -static void test_blocking_connection_errors(void) { - redisContext *c; - - test("Returns error when host cannot be resolved: "); - c = redisConnect((char*)"idontexist.test", 6379); - test_cond(c->err == REDIS_ERR_OTHER && - (strcmp(c->errstr,"Name or service not known") == 0 || - strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 || - strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || - strcmp(c->errstr,"No address associated with hostname") == 0 || - strcmp(c->errstr,"Temporary failure in name resolution") == 0 || - strcmp(c->errstr,"no address associated with name") == 0)); - redisFree(c); - - test("Returns error when the port is not open: "); - c = redisConnect((char*)"localhost", 1); - test_cond(c->err == REDIS_ERR_IO && - strcmp(c->errstr,"Connection refused") == 0); - redisFree(c); - - test("Returns error when the unix socket path doesn't accept connections: "); - c = redisConnectUnix((char*)"/tmp/idontexist.sock"); - test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ - redisFree(c); -} - -static void test_blocking_connection(struct config config) { - redisContext *c; - redisReply *reply; - - c = connect(config); - - test("Is able to deliver commands: "); - reply = redisCommand(c,"PING"); - test_cond(reply->type == REDIS_REPLY_STATUS && - strcasecmp(reply->str,"pong") == 0) - freeReplyObject(reply); - - test("Is a able to send commands verbatim: "); - reply = redisCommand(c,"SET foo bar"); - test_cond (reply->type == REDIS_REPLY_STATUS && - strcasecmp(reply->str,"ok") == 0) - freeReplyObject(reply); - - test("%%s String interpolation works: "); - reply = redisCommand(c,"SET %s %s","foo","hello world"); - freeReplyObject(reply); - reply = redisCommand(c,"GET foo"); - test_cond(reply->type == REDIS_REPLY_STRING && - strcmp(reply->str,"hello world") == 0); - freeReplyObject(reply); - - test("%%b String interpolation works: "); - reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11); - freeReplyObject(reply); - reply = redisCommand(c,"GET foo"); - test_cond(reply->type == REDIS_REPLY_STRING && - memcmp(reply->str,"hello\x00world",11) == 0) - - test("Binary reply length is correct: "); - test_cond(reply->len == 11) - freeReplyObject(reply); - - test("Can parse nil replies: "); - reply = redisCommand(c,"GET nokey"); - test_cond(reply->type == REDIS_REPLY_NIL) - freeReplyObject(reply); - - /* test 7 */ - test("Can parse integer replies: "); - reply = redisCommand(c,"INCR mycounter"); - test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) - freeReplyObject(reply); - - test("Can parse multi bulk replies: "); - freeReplyObject(redisCommand(c,"LPUSH mylist foo")); - freeReplyObject(redisCommand(c,"LPUSH mylist bar")); - reply = redisCommand(c,"LRANGE mylist 0 -1"); - test_cond(reply->type == REDIS_REPLY_ARRAY && - reply->elements == 2 && - !memcmp(reply->element[0]->str,"bar",3) && - !memcmp(reply->element[1]->str,"foo",3)) - freeReplyObject(reply); - - /* m/e with multi bulk reply *before* other reply. - * specifically test ordering of reply items to parse. */ - test("Can handle nested multi bulk replies: "); - freeReplyObject(redisCommand(c,"MULTI")); - freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1")); - freeReplyObject(redisCommand(c,"PING")); - reply = (redisCommand(c,"EXEC")); - test_cond(reply->type == REDIS_REPLY_ARRAY && - reply->elements == 2 && - reply->element[0]->type == REDIS_REPLY_ARRAY && - reply->element[0]->elements == 2 && - !memcmp(reply->element[0]->element[0]->str,"bar",3) && - !memcmp(reply->element[0]->element[1]->str,"foo",3) && - reply->element[1]->type == REDIS_REPLY_STATUS && - strcasecmp(reply->element[1]->str,"pong") == 0); - freeReplyObject(reply); - - disconnect(c, 0); -} - -static void test_blocking_connection_timeouts(struct config config) { - redisContext *c; - redisReply *reply; - ssize_t s; - const char *cmd = "DEBUG SLEEP 3\r\n"; - struct timeval tv; - - c = connect(config); - test("Successfully completes a command when the timeout is not exceeded: "); - reply = redisCommand(c,"SET foo fast"); - freeReplyObject(reply); - tv.tv_sec = 0; - tv.tv_usec = 10000; - redisSetTimeout(c, tv); - reply = redisCommand(c, "GET foo"); - test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0); - freeReplyObject(reply); - disconnect(c, 0); - - c = connect(config); - test("Does not return a reply when the command times out: "); - s = write(c->fd, cmd, strlen(cmd)); - tv.tv_sec = 0; - tv.tv_usec = 10000; - redisSetTimeout(c, tv); - reply = redisCommand(c, "GET foo"); - test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0); - freeReplyObject(reply); - - test("Reconnect properly reconnects after a timeout: "); - redisReconnect(c); - reply = redisCommand(c, "PING"); - test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); - freeReplyObject(reply); - - test("Reconnect properly uses owned parameters: "); - config.tcp.host = "foo"; - config.unix.path = "foo"; - redisReconnect(c); - reply = redisCommand(c, "PING"); - test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); - freeReplyObject(reply); - - disconnect(c, 0); -} - -static void test_blocking_io_errors(struct config config) { - redisContext *c; - redisReply *reply; - void *_reply; - int major, minor; - - /* Connect to target given by config. */ - c = connect(config); - { - /* Find out Redis version to determine the path for the next test */ - const char *field = "redis_version:"; - char *p, *eptr; - - reply = redisCommand(c,"INFO"); - p = strstr(reply->str,field); - major = strtol(p+strlen(field),&eptr,10); - p = eptr+1; /* char next to the first "." */ - minor = strtol(p,&eptr,10); - freeReplyObject(reply); - } - - test("Returns I/O error when the connection is lost: "); - reply = redisCommand(c,"QUIT"); - if (major > 2 || (major == 2 && minor > 0)) { - /* > 2.0 returns OK on QUIT and read() should be issued once more - * to know the descriptor is at EOF. */ - test_cond(strcasecmp(reply->str,"OK") == 0 && - redisGetReply(c,&_reply) == REDIS_ERR); - freeReplyObject(reply); - } else { - test_cond(reply == NULL); - } - - /* On 2.0, QUIT will cause the connection to be closed immediately and - * the read(2) for the reply on QUIT will set the error to EOF. - * On >2.0, QUIT will return with OK and another read(2) needed to be - * issued to find out the socket was closed by the server. In both - * conditions, the error will be set to EOF. */ - assert(c->err == REDIS_ERR_EOF && - strcmp(c->errstr,"Server closed the connection") == 0); - redisFree(c); - - c = connect(config); - test("Returns I/O error on socket timeout: "); - struct timeval tv = { 0, 1000 }; - assert(redisSetTimeout(c,tv) == REDIS_OK); - test_cond(redisGetReply(c,&_reply) == REDIS_ERR && - c->err == REDIS_ERR_IO && errno == EAGAIN); - redisFree(c); -} - -static void test_invalid_timeout_errors(struct config config) { - redisContext *c; - - test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: "); - - config.tcp.timeout.tv_sec = 0; - config.tcp.timeout.tv_usec = 10000001; - - c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); - - test_cond(c->err == REDIS_ERR_IO); - redisFree(c); - - test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); - - config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; - config.tcp.timeout.tv_usec = 0; - - c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); - - test_cond(c->err == REDIS_ERR_IO); - redisFree(c); -} - -static void test_throughput(struct config config) { - redisContext *c = connect(config); - redisReply **replies; - int i, num; - long long t1, t2; - - test("Throughput:\n"); - for (i = 0; i < 500; i++) - freeReplyObject(redisCommand(c,"LPUSH mylist foo")); - - num = 1000; - replies = malloc(sizeof(redisReply*)*num); - t1 = usec(); - for (i = 0; i < num; i++) { - replies[i] = redisCommand(c,"PING"); - assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); - - replies = malloc(sizeof(redisReply*)*num); - t1 = usec(); - for (i = 0; i < num; i++) { - replies[i] = redisCommand(c,"LRANGE mylist 0 499"); - assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); - assert(replies[i] != NULL && replies[i]->elements == 500); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); - - num = 10000; - replies = malloc(sizeof(redisReply*)*num); - for (i = 0; i < num; i++) - redisAppendCommand(c,"PING"); - t1 = usec(); - for (i = 0; i < num; i++) { - assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); - assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); - - replies = malloc(sizeof(redisReply*)*num); - for (i = 0; i < num; i++) - redisAppendCommand(c,"LRANGE mylist 0 499"); - t1 = usec(); - for (i = 0; i < num; i++) { - assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); - assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); - assert(replies[i] != NULL && replies[i]->elements == 500); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); - - disconnect(c, 0); -} - -// static long __test_callback_flags = 0; -// static void __test_callback(redisContext *c, void *privdata) { -// ((void)c); -// /* Shift to detect execution order */ -// __test_callback_flags <<= 8; -// __test_callback_flags |= (long)privdata; -// } -// -// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) { -// ((void)c); -// /* Shift to detect execution order */ -// __test_callback_flags <<= 8; -// __test_callback_flags |= (long)privdata; -// if (reply) freeReplyObject(reply); -// } -// -// static redisContext *__connect_nonblock() { -// /* Reset callback flags */ -// __test_callback_flags = 0; -// return redisConnectNonBlock("127.0.0.1", port, NULL); -// } -// -// static void test_nonblocking_connection() { -// redisContext *c; -// int wdone = 0; -// -// test("Calls command callback when command is issued: "); -// c = __connect_nonblock(); -// redisSetCommandCallback(c,__test_callback,(void*)1); -// redisCommand(c,"PING"); -// test_cond(__test_callback_flags == 1); -// redisFree(c); -// -// test("Calls disconnect callback on redisDisconnect: "); -// c = __connect_nonblock(); -// redisSetDisconnectCallback(c,__test_callback,(void*)2); -// redisDisconnect(c); -// test_cond(__test_callback_flags == 2); -// redisFree(c); -// -// test("Calls disconnect callback and free callback on redisFree: "); -// c = __connect_nonblock(); -// redisSetDisconnectCallback(c,__test_callback,(void*)2); -// redisSetFreeCallback(c,__test_callback,(void*)4); -// redisFree(c); -// test_cond(__test_callback_flags == ((2 << 8) | 4)); -// -// test("redisBufferWrite against empty write buffer: "); -// c = __connect_nonblock(); -// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1); -// redisFree(c); -// -// test("redisBufferWrite against not yet connected fd: "); -// c = __connect_nonblock(); -// redisCommand(c,"PING"); -// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && -// strncmp(c->error,"write:",6) == 0); -// redisFree(c); -// -// test("redisBufferWrite against closed fd: "); -// c = __connect_nonblock(); -// redisCommand(c,"PING"); -// redisDisconnect(c); -// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && -// strncmp(c->error,"write:",6) == 0); -// redisFree(c); -// -// test("Process callbacks in the right sequence: "); -// c = __connect_nonblock(); -// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); -// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); -// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); -// -// /* Write output buffer */ -// wdone = 0; -// while(!wdone) { -// usleep(500); -// redisBufferWrite(c,&wdone); -// } -// -// /* Read until at least one callback is executed (the 3 replies will -// * arrive in a single packet, causing all callbacks to be executed in -// * a single pass). */ -// while(__test_callback_flags == 0) { -// assert(redisBufferRead(c) == REDIS_OK); -// redisProcessCallbacks(c); -// } -// test_cond(__test_callback_flags == 0x010203); -// redisFree(c); -// -// test("redisDisconnect executes pending callbacks with NULL reply: "); -// c = __connect_nonblock(); -// redisSetDisconnectCallback(c,__test_callback,(void*)1); -// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); -// redisDisconnect(c); -// test_cond(__test_callback_flags == 0x0201); -// redisFree(c); -// } - -int main(int argc, char **argv) { - struct config cfg = { - .tcp = { - .host = "127.0.0.1", - .port = 6379 - }, - .unix = { - .path = "/tmp/redis.sock" - } - }; - int throughput = 1; - int test_inherit_fd = 1; - - /* Ignore broken pipe signal (for I/O error tests). */ - signal(SIGPIPE, SIG_IGN); - - /* Parse command line options. */ - argv++; argc--; - while (argc) { - if (argc >= 2 && !strcmp(argv[0],"-h")) { - argv++; argc--; - cfg.tcp.host = argv[0]; - } else if (argc >= 2 && !strcmp(argv[0],"-p")) { - argv++; argc--; - cfg.tcp.port = atoi(argv[0]); - } else if (argc >= 2 && !strcmp(argv[0],"-s")) { - argv++; argc--; - cfg.unix.path = argv[0]; - } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { - throughput = 0; - } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { - test_inherit_fd = 0; - } else { - fprintf(stderr, "Invalid argument: %s\n", argv[0]); - exit(1); - } - argv++; argc--; - } - - test_format_commands(); - test_reply_reader(); - test_blocking_connection_errors(); - test_free_null(); - - printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); - cfg.type = CONN_TCP; - test_blocking_connection(cfg); - test_blocking_connection_timeouts(cfg); - test_blocking_io_errors(cfg); - test_invalid_timeout_errors(cfg); - test_append_formatted_commands(cfg); - if (throughput) test_throughput(cfg); - - printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path); - cfg.type = CONN_UNIX; - test_blocking_connection(cfg); - test_blocking_connection_timeouts(cfg); - test_blocking_io_errors(cfg); - if (throughput) test_throughput(cfg); - - if (test_inherit_fd) { - printf("\nTesting against inherited fd (%s):\n", cfg.unix.path); - cfg.type = CONN_FD; - test_blocking_connection(cfg); - } - - - if (fails) { - printf("*** %d TESTS FAILED ***\n", fails); - return 1; - } - - printf("ALL TESTS PASSED\n"); - return 0; -} diff --git a/make-mac.mk b/make-mac.mk index fccfea7bf..6625dc85e 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -28,10 +28,9 @@ include objects.mk ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o ext/http-parser/http_parser.o ifeq ($(ZT_CONTROLLER),1) - LIBS+=-L/usr/local/opt/libpq/lib -lpq + LIBS+=-L/usr/local/opt/libpq/lib -lpq -Lext/redis-plus-plus-1.1.1/install/macos/lib -lredis++ -Lext/hiredis-0.14.1/lib/macos -lhiredis DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER_USE_REDIS -DZT_CONTROLLER - INCLUDES+=-I/usr/local/opt/libpq/include -Iext/hiredis-vip-0.3.0 - ONE_OBJS+=ext/hiredis-vip-0.3.0/adlist.o ext/hiredis-vip-0.3.0/async.o ext/hiredis-vip-0.3.0/command.o ext/hiredis-vip-0.3.0/crc16.o ext/hiredis-vip-0.3.0/dict.o ext/hiredis-vip-0.3.0/hiarray.o ext/hiredis-vip-0.3.0/hircluster.o ext/hiredis-vip-0.3.0/hiredis.o ext/hiredis-vip-0.3.0/hiutil.o ext/hiredis-vip-0.3.0/net.o ext/hiredis-vip-0.3.0/read.o ext/hiredis-vip-0.3.0/sds.o + INCLUDES+=-I/usr/local/opt/libpq/include -Iext/hiredis-0.14.1/include/ -Iext/redis-plus-plus-1.1.1/install/macos/include/sw/ endif # Official releases are signed with our Apple cert and apply software updates by default diff --git a/objects.mk b/objects.mk index ed415a508..efa2f3c0f 100644 --- a/objects.mk +++ b/objects.mk @@ -33,7 +33,6 @@ ONE_OBJS=\ controller/FileDB.o \ controller/LFDB.o \ controller/PostgreSQL.o \ - controller/Redis.o \ osdep/EthernetTap.o \ osdep/ManagedRoute.o \ osdep/Http.o \ From a50e8e9878b8e451862952c335b735dacfef11aa Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 12 May 2020 01:35:48 -0700 Subject: [PATCH 052/362] Add Bonds, Slaves, and Flows --- include/ZeroTierOne.h | 164 +++- node/Bond.cpp | 1730 +++++++++++++++++++++++++++++++++++ node/Bond.hpp | 689 ++++++++++++++ node/BondController.cpp | 203 ++++ node/BondController.hpp | 231 +++++ node/Constants.hpp | 452 ++++----- node/Flow.hpp | 123 +++ node/IncomingPacket.cpp | 284 ++++-- node/IncomingPacket.hpp | 11 +- node/Node.cpp | 52 +- node/Node.hpp | 13 +- node/Packet.cpp | 4 +- node/Packet.hpp | 40 +- node/Path.hpp | 809 ++++++++-------- node/Peer.cpp | 782 +++------------- node/Peer.hpp | 297 ++---- node/RingBuffer.hpp | 23 +- node/RuntimeEnvironment.hpp | 2 + node/Switch.cpp | 337 ++++--- node/Switch.hpp | 20 +- node/Trace.cpp | 17 +- node/Trace.hpp | 4 +- node/Utils.hpp | 11 +- objects.mk | 4 +- osdep/Binder.hpp | 29 +- osdep/LinuxNetLink.cpp | 16 - osdep/OSUtils.cpp | 20 +- osdep/OSUtils.hpp | 5 +- osdep/Phy.hpp | 44 +- osdep/Slave.hpp | 238 +++++ service/OneService.cpp | 210 ++++- 31 files changed, 4898 insertions(+), 1966 deletions(-) create mode 100644 node/Bond.cpp create mode 100644 node/Bond.hpp create mode 100644 node/BondController.cpp create mode 100644 node/BondController.hpp create mode 100644 node/Flow.hpp create mode 100644 osdep/Slave.hpp diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index e5667acc0..890e56048 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -415,55 +415,128 @@ enum ZT_ResultCode */ #define ZT_ResultCode_isFatal(x) ((((int)(x)) >= 100)&&(((int)(x)) < 1000)) + /** - * The multipath algorithm in use by this node. + * Multipath bonding policy */ -enum ZT_MultipathMode +enum ZT_MultipathBondingPolicy { /** - * No fault tolerance or balancing. + * Normal operation. No fault tolerance, no load balancing */ - ZT_MULTIPATH_NONE = 0, + ZT_BONDING_POLICY_NONE = 0, /** - * Sends traffic out on all paths. + * Sends traffic out on only one path at a time. Configurable immediate + * fail-over. */ - ZT_MULTIPATH_BROADCAST = 1, + ZT_BONDING_POLICY_ACTIVE_BACKUP = 1, /** - * Sends traffic out on only one path at a time. Immediate fail-over. + * Sends traffic out on all paths */ - ZT_MULTIPATH_ACTIVE_BACKUP= 2, + ZT_BONDING_POLICY_BROADCAST = 2, /** - * Sends traffic out on all interfaces according to a uniform random distribution. + * Stripes packets across all paths */ - ZT_MULTIPATH_BALANCE_RANDOM = 3, + ZT_BONDING_POLICY_BALANCE_RR = 3, /** - * Stripes packets across all paths. + * Packets destined for specific peers will always be sent over the same + * path. */ - ZT_MULTIPATH_BALANCE_RR_OPAQUE = 4, + ZT_BONDING_POLICY_BALANCE_XOR = 4, /** - * Balances flows across all paths. + * Balances flows among all paths according to path performance */ - ZT_MULTIPATH_BALANCE_RR_FLOW = 5, + ZT_BONDING_POLICY_BALANCE_AWARE = 5 +}; + +/** + * Multipath active re-selection policy (slaveSelectMethod) + */ +enum ZT_MultipathSlaveSelectMethod +{ + /** + * Primary slave regains status as active slave whenever it comes back up + * (default when slaves are explicitly specified) + */ + ZT_MULTIPATH_RESELECTION_POLICY_ALWAYS = 0, /** - * Hashes flows across all paths. + * Primary slave regains status as active slave when it comes back up and + * (if) it is better than the currently-active slave. */ - ZT_MULTIPATH_BALANCE_XOR_FLOW = 6, + ZT_MULTIPATH_RESELECTION_POLICY_BETTER = 1, /** - * Balances traffic across all paths according to observed performance. + * Primary slave regains status as active slave only if the currently-active + * slave fails. */ - ZT_MULTIPATH_BALANCE_DYNAMIC_OPAQUE = 7, + ZT_MULTIPATH_RESELECTION_POLICY_FAILURE = 2, /** - * Balances flows across all paths. + * The primary slave can change if a superior path is detected. + * (default if user provides no fail-over guidance) */ - ZT_MULTIPATH_BALANCE_DYNAMIC_FLOW = 8, + ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE = 3 +}; + +/** + * Mode of multipath slave interface + */ +enum ZT_MultipathSlaveMode +{ + ZT_MULTIPATH_SLAVE_MODE_PRIMARY = 0, + ZT_MULTIPATH_SLAVE_MODE_SPARE = 1 +}; + +/** + * Strategy for path monitoring + */ +enum ZT_MultipathMonitorStrategy +{ + /** + * Use bonding policy's default strategy + */ + ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DEFAULT = 0, + + /** + * Does not actively send probes to judge aliveness, will rely + * on conventional traffic and summary statistics. + */ + ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_PASSIVE = 1, + + /** + * Sends probes at a constant rate to judge aliveness. + */ + ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_ACTIVE = 2, + + /** + * Sends probes at varying rates which correlate to native + * traffic loads to judge aliveness. + */ + ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC = 3 +}; + +/** + * Indices for the path quality weight vector + */ +enum ZT_MultipathQualityWeightIndex +{ + ZT_QOS_LAT_IDX, + ZT_QOS_LTM_IDX, + ZT_QOS_PDV_IDX, + ZT_QOS_PLR_IDX, + ZT_QOS_PER_IDX, + ZT_QOS_THR_IDX, + ZT_QOS_THM_IDX, + ZT_QOS_THV_IDX, + ZT_QOS_AGE_IDX, + ZT_QOS_SCP_IDX, + ZT_QOS_WEIGHT_SIZE }; /** @@ -1272,44 +1345,49 @@ typedef struct uint64_t trustedPathId; /** - * One-way latency + * Mean latency */ - float latency; + float latencyMean; /** - * How much latency varies over time + * Maximum observed latency */ - float packetDelayVariance; + float latencyMax; /** - * How much observed throughput varies over time + * Variance of latency */ - float throughputDisturbCoeff; + float latencyVariance; /** - * Packet Error Ratio (PER) - */ - float packetErrorRatio; - - /** - * Packet Loss Ratio (PLR) + * Packet loss ratio */ float packetLossRatio; /** - * Stability of the path + * Packet error ratio */ - float stability; + float packetErrorRatio; /** - * Current throughput (moving average) + * Mean throughput */ - uint64_t throughput; + uint64_t throughputMean; /** - * Maximum observed throughput for this path + * Maximum observed throughput */ - uint64_t maxThroughput; + float throughputMax; + + /** + * Throughput variance + */ + float throughputVariance; + + /** + * Address scope + */ + uint8_t scope; /** * Percentage of traffic allocated to this path @@ -1319,7 +1397,9 @@ typedef struct /** * Name of physical interface (for monitoring) */ - char *ifname; + char ifname[32]; + + uint64_t localSocket; /** * Is path expired? @@ -1373,9 +1453,11 @@ typedef struct unsigned int pathCount; /** - * Whether this peer was ever reachable via an aggregate link + * Whether multiple paths to this peer are bonded */ - bool hadAggregateLink; + bool isBonded; + + int bondingPolicy; /** * Known network paths to peer diff --git a/node/Bond.cpp b/node/Bond.cpp new file mode 100644 index 000000000..9a5ab1df8 --- /dev/null +++ b/node/Bond.cpp @@ -0,0 +1,1730 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2024-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +#include + +#include "Peer.hpp" +#include "Bond.hpp" +#include "Switch.hpp" +#include "Flow.hpp" +#include "Path.hpp" + +namespace ZeroTier { + +Bond::Bond(const RuntimeEnvironment *renv, int policy, const SharedPtr& peer) : + RR(renv), + _peer(peer) +{ + setReasonableDefaults(policy); + _policyAlias = BondController::getPolicyStrByCode(policy); +} + +Bond::Bond(std::string& basePolicy, std::string& policyAlias, const SharedPtr& peer) : + _policyAlias(policyAlias), + _peer(peer) +{ + setReasonableDefaults(BondController::getPolicyCodeByStr(basePolicy)); +} + +Bond::Bond(const RuntimeEnvironment *renv, const Bond &originalBond, const SharedPtr& peer) : + RR(renv), + _peer(peer) +{ + // First, set everything to sane defaults + setReasonableDefaults(originalBond._bondingPolicy); + _policyAlias = originalBond._policyAlias; + // Second, apply user specified values (only if they make sense) + _downDelay = originalBond._downDelay; + _upDelay = originalBond._upDelay; + if (originalBond._bondMonitorInterval > 0 && originalBond._bondMonitorInterval < 65535) { + _bondMonitorInterval = originalBond._bondMonitorInterval; + } + else { + fprintf(stderr, "warning: bondMonitorInterval (%d) is out of range, using default (%d)\n", originalBond._bondMonitorInterval, _bondMonitorInterval); + } + if (originalBond._slaveMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_PASSIVE + && originalBond._failoverInterval != 0) { + fprintf(stderr, "warning: passive path monitoring was specified, this will prevent failovers from happening in a timely manner.\n"); + } + _abSlaveSelectMethod = originalBond._abSlaveSelectMethod; + memcpy(_qualityWeights, originalBond._qualityWeights, ZT_QOS_WEIGHT_SIZE * sizeof(float)); +} + +void Bond::nominatePath(const SharedPtr& path, int64_t now) +{ + char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "nominatePath: %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + Mutex::Lock _l(_paths_m); + if (!RR->bc->slaveAllowed(_policyAlias, getSlave(path))) { + return; + } + bool alreadyPresent = false; + for (int i=0; islave = RR->bc->getSlaveBySocket(_policyAlias, path->localSocket()); + _paths[i]->startTrial(now); + break; + } + } + } + curateBond(now, true); + estimatePathQuality(now); +} + +SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) +{ + Mutex::Lock _l(_paths_m); + /** + * active-backup + */ + if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP) { + if (_abPath) { + return _abPath; + } + } + /** + * broadcast + */ + if (_bondingPolicy== ZT_BONDING_POLICY_BROADCAST) { + return SharedPtr(); // Handled in Switch::_trySend() + } + if (!_numBondedPaths) { + return SharedPtr(); // No paths assigned to bond yet, cannot balance traffic + } + /** + * balance-rr + */ + if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_RR) { + if (!_allowFlowHashing) { + //fprintf(stderr, "_rrPacketsSentOnCurrSlave=%d, _numBondedPaths=%d, _rrIdx=%d\n", _rrPacketsSentOnCurrSlave, _numBondedPaths, _rrIdx); + if (_packetsPerSlave == 0) { + // Randomly select a path + return _paths[_bondedIdx[_freeRandomByte % _numBondedPaths]]; // TODO: Optimize + } + if (_rrPacketsSentOnCurrSlave < _packetsPerSlave) { + // Continue to use this slave + ++_rrPacketsSentOnCurrSlave; + return _paths[_bondedIdx[_rrIdx]]; + } + // Reset striping counter + _rrPacketsSentOnCurrSlave = 0; + if (_numBondedPaths == 1) { + _rrIdx = 0; + } + else { + int _tempIdx = _rrIdx; + for (int searchCount = 0; searchCount < (_numBondedPaths-1); searchCount++) { + _tempIdx = (_tempIdx == (_numBondedPaths-1)) ? 0 : _tempIdx+1; + if (_paths[_bondedIdx[_tempIdx]] && _paths[_bondedIdx[_tempIdx]]->eligible(now,_ackSendInterval)) { + _rrIdx = _tempIdx; + break; + } + } + } + //fprintf(stderr, "resultant _rrIdx=%d\n", _rrIdx); + if (_paths[_bondedIdx[_rrIdx]]) { + return _paths[_bondedIdx[_rrIdx]]; + } + } + } + /** + * balance-xor + */ + if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_XOR || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE) { + if (!_allowFlowHashing || flowId == -1) { + // No specific path required for unclassified traffic, send on anything + return _paths[_bondedIdx[_freeRandomByte % _numBondedPaths]]; // TODO: Optimize + } + else if (_allowFlowHashing) { + // TODO: Optimize + Mutex::Lock _l(_flows_m); + SharedPtr flow; + if (_flows.count(flowId)) { + flow = _flows[flowId]; + flow->updateActivity(now); + } + else { + unsigned char entropy; + Utils::getSecureRandom(&entropy, 1); + flow = createFlow(SharedPtr(), flowId, entropy, now); + } + if (flow) { + return flow->assignedPath(); + } + } + } + return SharedPtr(); +} + +void Bond::recordIncomingInvalidPacket(const SharedPtr& path) +{ + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordIncomingInvalidPacket() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + Mutex::Lock _l(_paths_m); + for (int i=0; ipacketValiditySamples.push(false); + } + } +} + +void Bond::recordOutgoingPacket(const SharedPtr &path, const uint64_t packetId, + uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now) +{ + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordOutgoingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getSlave(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); + _freeRandomByte += (unsigned char)(packetId >> 8); // Grab entropy to use in path selection logic + if (!_shouldCollectPathStatistics) { + return; + } + bool isFrame = (verb == Packet::VERB_FRAME || verb == Packet::VERB_EXT_FRAME); + bool shouldRecord = (packetId & (ZT_QOS_ACK_DIVISOR - 1) + && (verb != Packet::VERB_ACK) + && (verb != Packet::VERB_QOS_MEASUREMENT)); + if (isFrame || shouldRecord) { + Mutex::Lock _l(_paths_m); + if (isFrame) { + ++(path->_packetsOut); + _lastFrame=now; + } + if (shouldRecord) { + path->_unackedBytes += payloadLength; + // Take note that we're expecting a VERB_ACK on this path as of a specific time + if (path->qosStatsOut.size() < ZT_QOS_MAX_OUTSTANDING_RECORDS) { + path->qosStatsOut[packetId] = now; + } + } + } + if (_allowFlowHashing) { + if (_allowFlowHashing && (flowId != ZT_QOS_NO_FLOW)) { + Mutex::Lock _l(_flows_m); + if (_flows.count(flowId)) { + _flows[flowId]->recordOutgoingBytes(payloadLength); + } + } + } +} + +void Bond::recordIncomingPacket(const SharedPtr& path, uint64_t packetId, uint16_t payloadLength, + Packet::Verb verb, int32_t flowId, int64_t now) +{ + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "recordIncomingPacket() %s %s, packetId=%llx, payloadLength=%d, verb=%x, flowId=%lx\n", getSlave(path)->ifname().c_str(), pathStr, packetId, payloadLength, verb, flowId); + bool isFrame = (verb == Packet::VERB_FRAME || verb == Packet::VERB_EXT_FRAME); + bool shouldRecord = (packetId & (ZT_QOS_ACK_DIVISOR - 1) + && (verb != Packet::VERB_ACK) + && (verb != Packet::VERB_QOS_MEASUREMENT)); + if (isFrame || shouldRecord) { + Mutex::Lock _l(_paths_m); + if (isFrame) { + ++(path->_packetsIn); + _lastFrame=now; + } + if (shouldRecord) { + path->ackStatsIn[packetId] = payloadLength; + ++(path->_packetsReceivedSinceLastAck); + path->qosStatsIn[packetId] = now; + ++(path->_packetsReceivedSinceLastQoS); + path->packetValiditySamples.push(true); + } + } + /** + * Learn new flows and pro-actively create entries for them in the bond so + * that the next time we send a packet out that is part of a flow we know + * which path to use. + */ + if ((flowId != ZT_QOS_NO_FLOW) + && (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_RR + || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_XOR + || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE)) { + Mutex::Lock _l(_flows_m); + SharedPtr flow; + if (!_flows.count(flowId)) { + flow = createFlow(path, flowId, 0, now); + } else { + flow = _flows[flowId]; + } + if (flow) { + flow->recordIncomingBytes(payloadLength); + } + } +} + +void Bond::receivedQoS(const SharedPtr& path, int64_t now, int count, uint64_t *rx_id, uint16_t *rx_ts) +{ + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "receivedQoS() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + Mutex::Lock _l(_paths_m); + // Look up egress times and compute latency values for each record + std::map::iterator it; + for (int j=0; jqosStatsOut.find(rx_id[j]); + if (it != path->qosStatsOut.end()) { + path->latencySamples.push(((uint16_t)(now - it->second) - rx_ts[j]) / 2); + path->qosStatsOut.erase(it); + } + } + path->qosRecordSize.push(count); + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "receivedQoS() on path %s %s, count=%d, successful=%d, qosStatsOut.size()=%d\n", getSlave(path)->ifname().c_str(), pathStr, count, path->aknowledgedQoSRecordCountSinceLastCheck, path->qosStatsOut.size()); +} + +void Bond::receivedAck(const SharedPtr& path, int64_t now, int32_t ackedBytes) +{ + Mutex::Lock _l(_paths_m); + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "receivedAck() %s %s, (ackedBytes=%d, lastAckReceived=%lld, ackAge=%lld)\n", getSlave(path)->ifname().c_str(), pathStr, ackedBytes, path->lastAckReceived, path->ackAge(now)); + path->_lastAckReceived = now; + path->_unackedBytes = (ackedBytes > path->_unackedBytes) ? 0 : path->_unackedBytes - ackedBytes; + int64_t timeSinceThroughputEstimate = (now - path->_lastThroughputEstimation); + if (timeSinceThroughputEstimate >= throughputMeasurementInterval) { + // TODO: See if this floating point math can be reduced + uint64_t throughput = (uint64_t)((float)(path->_bytesAckedSinceLastThroughputEstimation) / ((float)timeSinceThroughputEstimate / (float)1000)); + throughput /= 1000; + if (throughput > 0.0) { + path->throughputSamples.push(throughput); + path->_throughputMax = throughput > path->_throughputMax ? throughput : path->_throughputMax; + } + path->_lastThroughputEstimation = now; + path->_bytesAckedSinceLastThroughputEstimation = 0; + } else { + path->_bytesAckedSinceLastThroughputEstimation += ackedBytes; + } +} + +int32_t Bond::generateQoSPacket(const SharedPtr& path, int64_t now, char *qosBuffer) +{ + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "generateQoSPacket() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + int32_t len = 0; + std::map::iterator it = path->qosStatsIn.begin(); + int i=0; + int numRecords = std::min(path->_packetsReceivedSinceLastQoS,ZT_QOS_TABLE_SIZE); + while (iqosStatsIn.end()) { + uint64_t id = it->first; + memcpy(qosBuffer, &id, sizeof(uint64_t)); + qosBuffer+=sizeof(uint64_t); + uint16_t holdingTime = (uint16_t)(now - it->second); + memcpy(qosBuffer, &holdingTime, sizeof(uint16_t)); + qosBuffer+=sizeof(uint16_t); + len+=sizeof(uint64_t)+sizeof(uint16_t); + path->qosStatsIn.erase(it++); + ++i; + } + return len; +} + +bool Bond::assignFlowToBondedPath(SharedPtr &flow, int64_t now) +{ + //fprintf(stderr, "assignFlowToBondedPath\n"); + char curPathStr[128]; + unsigned int idx = ZT_MAX_PEER_NETWORK_PATHS; + if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_XOR) { + idx = abs((int)(flow->id() % (_numBondedPaths))); + flow->assignPath(_paths[_bondedIdx[idx]],now); + } + if (_bondingPolicy == ZT_BONDING_POLICY_BALANCE_AWARE) { + unsigned char entropy; + Utils::getSecureRandom(&entropy, 1); + if (_totalBondUnderload) { + entropy %= _totalBondUnderload; + } + if (!_numBondedPaths) { + fprintf(stderr, "no bonded paths for flow assignment\n"); + return false; + } + for(unsigned int i=0;ibonded()) { + SharedPtr slave = RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + _paths[i]->address().toString(curPathStr); + uint8_t probabilitySegment = (_totalBondUnderload > 0) ? _paths[i]->_affinity : _paths[i]->_allocation; + //fprintf(stderr, "i=%2d, entropy=%3d, alloc=%3d, byteload=%4d, segment=%3d, _totalBondUnderload=%3d, ifname=%s, path=%20s\n", i, entropy, _paths[i]->allocation, _paths[i]->relativeByteLoad, probabilitySegment, _totalBondUnderload, slave->ifname().c_str(), curPathStr); + if (entropy <= probabilitySegment) { + idx = i; + //fprintf(stderr, "\t is best path\n"); + break; + } + entropy -= probabilitySegment; + } + } + if (idx < ZT_MAX_PEER_NETWORK_PATHS) { + flow->assignPath(_paths[idx],now); + ++(_paths[idx]->_assignedFlowCount); + } + else { + fprintf(stderr, "could not assign flow?\n"); exit(0); // TODO: Remove + return false; + } + } + flow->assignedPath()->address().toString(curPathStr); + SharedPtr slave = RR->bc->getSlaveBySocket(_policyAlias, flow->assignedPath()->localSocket()); + fprintf(stderr, "assigned (tx) flow %x with peer %llx to path %s on %s (idx=%d)\n", flow->id(), _peer->_id.address().toInt(), curPathStr, slave->ifname().c_str(), idx); + return true; +} + +SharedPtr Bond::createFlow(const SharedPtr &path, int32_t flowId, unsigned char entropy, int64_t now) +{ + //fprintf(stderr, "createFlow\n"); + char curPathStr[128]; + // --- + if (!_numBondedPaths) { + fprintf(stderr, "there are no bonded paths, cannot assign flow\n"); + return SharedPtr(); + } + if (_flows.size() >= ZT_FLOW_MAX_COUNT) { + fprintf(stderr, "max number of flows reached (%d), forcibly forgetting oldest flow\n", ZT_FLOW_MAX_COUNT); + forgetFlowsWhenNecessary(0,true,now); + } + SharedPtr flow = new Flow(flowId, now); + _flows[flowId] = flow; + fprintf(stderr, "new flow %x detected with peer %llx, %lu active flow(s)\n", flowId, _peer->_id.address().toInt(), (_flows.size())); + /** + * Add a flow with a given Path already provided. This is the case when a packet + * is received on a path but no flow exists, in this case we simply assign the path + * that the remote peer chose for us. + */ + if (path) { + flow->assignPath(path,now); + path->address().toString(curPathStr); + SharedPtr slave = RR->bc->getSlaveBySocket(_policyAlias, flow->assignedPath()->localSocket()); + fprintf(stderr, "assigned (rx) flow %x with peer %llx to path %s on %s\n", flow->id(), _peer->_id.address().toInt(), curPathStr, slave->ifname().c_str()); + } + /** + * Add a flow when no path was provided. This means that it is an outgoing packet + * and that it is up to the local peer to decide how to load-balance its transmission. + */ + else if (!path) { + assignFlowToBondedPath(flow, now); + } + return flow; +} + +void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) +{ + //fprintf(stderr, "forgetFlowsWhenNecessary\n"); + std::map >::iterator it = _flows.begin(); + std::map >::iterator oldestFlow = _flows.end(); + SharedPtr expiredFlow; + if (age) { // Remove by specific age + while (it != _flows.end()) { + if (it->second->age(now) > age) { + fprintf(stderr, "forgetting flow %x between this node and %llx, %lu active flow(s)\n", it->first, _peer->_id.address().toInt(), (_flows.size()-1)); + it = _flows.erase(it); + } else { + ++it; + } + } + } + else if (oldest) { // Remove single oldest by natural expiration + uint64_t maxAge = 0; + while (it != _flows.end()) { + if (it->second->age(now) > maxAge) { + maxAge = (now - it->second->age(now)); + oldestFlow = it; + } + ++it; + } + if (oldestFlow != _flows.end()) { + fprintf(stderr, "forgetting oldest flow %x (of age %llu) between this node and %llx, %lu active flow(s)\n", oldestFlow->first, oldestFlow->second->age(now), _peer->_id.address().toInt(), (_flows.size()-1)); + _flows.erase(oldestFlow); + } + } + fprintf(stderr, "000\n"); +} + +void Bond::processIncomingPathNegotiationRequest(uint64_t now, SharedPtr &path, int16_t remoteUtility) +{ + //fprintf(stderr, "processIncomingPathNegotiationRequest\n"); + if (_abSlaveSelectMethod != ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { + return; + } + Mutex::Lock _l(_paths_m); + char pathStr[128]; + path->address().toString(pathStr); + if (!_lastPathNegotiationCheck) { + return; + } + SharedPtr slave = RR->bc->getSlaveBySocket(_policyAlias, path->localSocket()); + if (remoteUtility > _localUtility) { + fprintf(stderr, "peer suggests path, its utility (%d) is greater than ours (%d), we will switch to %s on %s (ls=%llx)\n", remoteUtility, _localUtility, pathStr, slave->ifname().c_str(), path->localSocket()); + negotiatedPath = path; + } + if (remoteUtility < _localUtility) { + fprintf(stderr, "peer suggests path, its utility (%d) is less than ours (%d), we will NOT switch to %s on %s (ls=%llx)\n", remoteUtility, _localUtility, pathStr, slave->ifname().c_str(), path->localSocket()); + } + if (remoteUtility == _localUtility) { + fprintf(stderr, "peer suggest path, but utility is equal, picking choice made by peer with greater identity.\n"); + if (_peer->_id.address().toInt() > RR->node->identity().address().toInt()) { + fprintf(stderr, "peer identity was greater, going with their choice of %s on %s (ls=%llx)\n", pathStr, slave->ifname().c_str(), path->localSocket()); + negotiatedPath = path; + } else { + fprintf(stderr, "our identity was greater, no change\n"); + } + } +} + +void Bond::pathNegotiationCheck(void *tPtr, const int64_t now) +{ + //fprintf(stderr, "pathNegotiationCheck\n"); + char pathStr[128]; + int maxInPathIdx = ZT_MAX_PEER_NETWORK_PATHS; + int maxOutPathIdx = ZT_MAX_PEER_NETWORK_PATHS; + uint64_t maxInCount = 0; + uint64_t maxOutCount = 0; + for(unsigned int i=0;i_packetsIn > maxInCount) { + maxInCount = _paths[i]->_packetsIn; + maxInPathIdx = i; + } + if (_paths[i]->_packetsOut > maxOutCount) { + maxOutCount = _paths[i]->_packetsOut; + maxOutPathIdx = i; + } + _paths[i]->resetPacketCounts(); + } + bool _peerLinksSynchronized = ((maxInPathIdx != ZT_MAX_PEER_NETWORK_PATHS) + && (maxOutPathIdx != ZT_MAX_PEER_NETWORK_PATHS) + && (maxInPathIdx != maxOutPathIdx)) ? false : true; + /** + * Determine utility and attempt to petition remote peer to switch to our chosen path + */ + if (!_peerLinksSynchronized) { + _localUtility = _paths[maxOutPathIdx]->_failoverScore - _paths[maxInPathIdx]->_failoverScore; + if (_paths[maxOutPathIdx]->_negotiated) { + _localUtility -= ZT_MULTIPATH_FAILOVER_HANDICAP_NEGOTIATED; + } + if ((now - _lastSentPathNegotiationRequest) > ZT_PATH_NEGOTIATION_CUTOFF_TIME) { + fprintf(stderr, "BT: (sync) it's been long enough, sending more requests.\n"); + _numSentPathNegotiationRequests = 0; + } + if (_numSentPathNegotiationRequests < ZT_PATH_NEGOTIATION_TRY_COUNT) { + if (_localUtility >= 0) { + fprintf(stderr, "BT: (sync) paths appear to be out of sync (utility=%d)\n", _localUtility); + sendPATH_NEGOTIATION_REQUEST(tPtr, _paths[maxOutPathIdx]); + ++_numSentPathNegotiationRequests; + _lastSentPathNegotiationRequest = now; + _paths[maxOutPathIdx]->address().toString(pathStr); + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[maxOutPathIdx]->localSocket()); + fprintf(stderr, "sending request to use %s on %s, ls=%llx, utility=%d\n", pathStr, slave->ifname().c_str(), _paths[maxOutPathIdx]->localSocket(), _localUtility); + } + } + /** + * Give up negotiating and consider switching + */ + else if ((now - _lastSentPathNegotiationRequest) > (2 * ZT_PATH_NEGOTIATION_CHECK_INTERVAL)) { + if (_localUtility == 0) { + // There's no loss to us, just switch without sending a another request + fprintf(stderr, "BT: (sync) giving up, switching to remote peer's path.\n"); + negotiatedPath = _paths[maxInPathIdx]; + } + } + } +} + +void Bond::sendPATH_NEGOTIATION_REQUEST(void *tPtr, const SharedPtr &path) +{ + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "sendPATH_NEGOTIATION_REQUEST() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + if (_abSlaveSelectMethod != ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { + return; + } + Packet outp(_peer->_id.address(),RR->identity.address(),Packet::VERB_PATH_NEGOTIATION_REQUEST); + outp.append(_localUtility); + if (path->address()) { + outp.armor(_peer->key(),false); + RR->node->putPacket(tPtr,path->localSocket(),path->address(),outp.data(),outp.size()); + } +} + +void Bond::sendACK(void *tPtr,const SharedPtr &path,const int64_t localSocket, + const InetAddress &atAddress,int64_t now) +{ + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "sendACK() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + Packet outp(_peer->_id.address(),RR->identity.address(),Packet::VERB_ACK); + int32_t bytesToAck = 0; + std::map::iterator it = path->ackStatsIn.begin(); + while (it != path->ackStatsIn.end()) { + bytesToAck += it->second; + ++it; + } + outp.append(bytesToAck); + if (atAddress) { + outp.armor(_peer->key(),false); + RR->node->putPacket(tPtr,localSocket,atAddress,outp.data(),outp.size()); + } else { + RR->sw->send(tPtr,outp,false); + } + path->ackStatsIn.clear(); + path->_packetsReceivedSinceLastAck = 0; + path->_lastAckSent = now; +} + +void Bond::sendQOS_MEASUREMENT(void *tPtr,const SharedPtr &path,const int64_t localSocket, + const InetAddress &atAddress,int64_t now) +{ + //char pathStr[128];path->address().toString(pathStr);fprintf(stderr, "sendQOS() %s %s\n", getSlave(path)->ifname().c_str(), pathStr); + const int64_t _now = RR->node->now(); + Packet outp(_peer->_id.address(),RR->identity.address(),Packet::VERB_QOS_MEASUREMENT); + char qosData[ZT_QOS_MAX_PACKET_SIZE]; + int16_t len = generateQoSPacket(path, _now,qosData); + outp.append(qosData,len); + if (atAddress) { + outp.armor(_peer->key(),false); + RR->node->putPacket(tPtr,localSocket,atAddress,outp.data(),outp.size()); + } else { + RR->sw->send(tPtr,outp,false); + } + // Account for the fact that a VERB_QOS_MEASUREMENT was just sent. Reset timers. + path->_packetsReceivedSinceLastQoS = 0; + path->_lastQoSMeasurement = now; +} + +void Bond::processBackgroundTasks(void *tPtr, const int64_t now) +{ + Mutex::Lock _l(_paths_m); + if (!_peer->_canUseMultipath || (now - _lastBackgroundTaskCheck) < ZT_BOND_BACKGROUND_TASK_MIN_INTERVAL) { + return; + } + _lastBackgroundTaskCheck = now; + + // Compute dynamic path monitor timer interval + if (_slaveMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC) { + int suggestedMonitorInterval = (now - _lastFrame) / 100; + _dynamicPathMonitorInterval = std::min(ZT_PATH_HEARTBEAT_PERIOD, ((suggestedMonitorInterval > _bondMonitorInterval) ? suggestedMonitorInterval : _bondMonitorInterval)); + //fprintf(stderr, "_lastFrame=%llu, suggestedMonitorInterval=%d, _dynamicPathMonitorInterval=%d\n", + // (now-_lastFrame), suggestedMonitorInterval, _dynamicPathMonitorInterval); + } + + if (_slaveMonitorStrategy == ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC) { + _shouldCollectPathStatistics = true; + } + + // Memoize oft-used properties in the packet ingress/egress logic path + if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE) { + // Required for real-time balancing + _shouldCollectPathStatistics = true; + } + if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP) { + if (_abSlaveSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_BETTER) { + // Required for judging suitability of primary slave after recovery + _shouldCollectPathStatistics = true; + } + if (_abSlaveSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { + // Required for judging suitability of new candidate primary + _shouldCollectPathStatistics = true; + } + } + if ((now - _lastCheckUserPreferences) > 1000) { + _lastCheckUserPreferences = now; + applyUserPrefs(); + } + + curateBond(now,false); + if ((now - _lastQualityEstimation) > _qualityEstimationInterval) { + _lastQualityEstimation = now; + estimatePathQuality(now); + } + dumpInfo(now); + + // Send QOS/ACK packets as needed + if (_shouldCollectPathStatistics) { + for(unsigned int i=0;iallowed()) { + if (_paths[i]->needsToSendQoS(now,_qosSendInterval)) { + sendQOS_MEASUREMENT(tPtr, _paths[i], _paths[i]->localSocket(), _paths[i]->address(), now); + } + if (_paths[i]->needsToSendAck(now,_ackSendInterval)) { + sendACK(tPtr, _paths[i], _paths[i]->localSocket(), _paths[i]->address(), now); + } + } + } + } + // Perform periodic background tasks unique to each bonding policy + switch (_bondingPolicy) + { + case ZT_BONDING_POLICY_ACTIVE_BACKUP: + processActiveBackupTasks(now); + break; + case ZT_BONDING_POLICY_BROADCAST: + break; + case ZT_BONDING_POLICY_BALANCE_RR: + case ZT_BONDING_POLICY_BALANCE_XOR: + case ZT_BONDING_POLICY_BALANCE_AWARE: + processBalanceTasks(now); + break; + default: + break; + } + // Check whether or not a path negotiation needs to be performed + if (((now - _lastPathNegotiationCheck) > ZT_PATH_NEGOTIATION_CHECK_INTERVAL) && _allowPathNegotiation) { + _lastPathNegotiationCheck = now; + pathNegotiationCheck(tPtr, now); + } +} + +void Bond::applyUserPrefs() +{ + fprintf(stderr, "applyUserPrefs, _minReqPathMonitorInterval=%d\n", RR->bc->minReqPathMonitorInterval()); + for(unsigned int i=0;i sl = getSlave(_paths[i]); + if (sl) { + if (sl->monitorInterval() == 0) { // If no interval was specified for this slave, use more generic bond-wide interval + sl->setMonitorInterval(_bondMonitorInterval); + } + RR->bc->setMinReqPathMonitorInterval((sl->monitorInterval() < RR->bc->minReqPathMonitorInterval()) ? sl->monitorInterval() : RR->bc->minReqPathMonitorInterval()); + bool bFoundCommonSlave = false; + SharedPtr commonSlave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + for(unsigned int j=0;jbc->getSlaveBySocket(_policyAlias, _paths[j]->localSocket()) == commonSlave) { + bFoundCommonSlave = true; + } + } + } + _paths[i]->_monitorInterval = sl->monitorInterval(); + _paths[i]->_upDelay = sl->upDelay() ? sl->upDelay() : _upDelay; + _paths[i]->_downDelay = sl->downDelay() ? sl->downDelay() : _downDelay; + _paths[i]->_ipvPref = sl->ipvPref(); + _paths[i]->_mode = sl->mode(); + _paths[i]->_enabled = sl->enabled(); + _paths[i]->_onlyPathOnSlave = !bFoundCommonSlave; + } + } + if (_peer) { + _peer->_shouldCollectPathStatistics = _shouldCollectPathStatistics; + _peer->_bondingPolicy = _bondingPolicy; + } +} + +void Bond::curateBond(const int64_t now, bool rebuildBond) +{ + //fprintf(stderr, "%lu curateBond (rebuildBond=%d)\n", ((now - RR->bc->getBondStartTime())), rebuildBond); + char pathStr[128]; + /** + * Update path states + */ + for(unsigned int i=0;ieligible(now,_ackSendInterval); + if (currEligibility != _paths[i]->_lastEligibilityState) { + _paths[i]->address().toString(pathStr); + //fprintf(stderr, "\n\n%ld path eligibility (for %s, %s) has changed (from %d to %d)\n", (RR->node->now() - RR->bc->getBondStartTime()), getSlave(_paths[i])->ifname().c_str(), pathStr, _paths[i]->lastCheckedEligibility, _paths[i]->eligible(now,_ackSendInterval)); + if (currEligibility) { + rebuildBond = true; + } + if (!currEligibility) { + _paths[i]->adjustRefractoryPeriod(now, _defaultPathRefractoryPeriod, !currEligibility); + if (_paths[i]->bonded()) { + //fprintf(stderr, "the path was bonded, reallocation of its flows will occur soon\n"); + rebuildBond = true; + _paths[i]->_shouldReallocateFlows = _paths[i]->bonded(); + _paths[i]->setBonded(false); + } else { + //fprintf(stderr, "the path was not bonded, no consequences\n"); + } + } + } + if (currEligibility) { + _paths[i]->adjustRefractoryPeriod(now, _defaultPathRefractoryPeriod, false); + } + _paths[i]->_lastEligibilityState = currEligibility; + } + /** + * Curate the set of paths that are part of the bond proper. Selects a single path + * per logical slave according to eligibility and user-specified constraints. + */ + if ((_bondingPolicy== ZT_BONDING_POLICY_BALANCE_RR) + || (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_XOR) + || (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE)) { + if (!_numBondedPaths) { + rebuildBond = true; + } + // TODO: Optimize + if (rebuildBond) { + int updatedBondedPathCount = 0; + std::map,int> slaveMap; + for (int i=0;iallowed() && (_paths[i]->eligible(now,_ackSendInterval) || !_numBondedPaths)) { + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + if (!slaveMap.count(slave)) { + slaveMap[slave] = i; + } + else { + bool overriden = false; + _paths[i]->address().toString(pathStr); + //fprintf(stderr, " slave representative path already exists! (%s %s)\n", getSlave(_paths[i])->ifname().c_str(), pathStr); + if (_paths[i]->preferred() && !_paths[slaveMap[slave]]->preferred()) { + // Override previous choice if preferred + //fprintf(stderr, "overriding since its preferred!\n"); + if (_paths[slaveMap[slave]]->_assignedFlowCount) { + _paths[slaveMap[slave]]->_deprecated = true; + } + else { + _paths[slaveMap[slave]]->_deprecated = true; + _paths[slaveMap[slave]]->setBonded(false); + } + slaveMap[slave] = i; + overriden = true; + } + if ((_paths[i]->preferred() && _paths[slaveMap[slave]]->preferred()) + || (!_paths[i]->preferred() && !_paths[slaveMap[slave]]->preferred())) { + if (_paths[i]->preferenceRank() > _paths[slaveMap[slave]]->preferenceRank()) { + // Override if higher preference + //fprintf(stderr, "overriding according to preference preferenceRank!\n"); + if (_paths[slaveMap[slave]]->_assignedFlowCount) { + _paths[slaveMap[slave]]->_deprecated = true; + } + else { + _paths[slaveMap[slave]]->_deprecated = true; + _paths[slaveMap[slave]]->setBonded(false); + } + slaveMap[slave] = i; + } + } + } + } + } + std::map,int>::iterator it = slaveMap.begin(); + for (int i=0; isecond; + _paths[_bondedIdx[i]]->setBonded(true); + ++it; + ++updatedBondedPathCount; + _paths[_bondedIdx[i]]->address().toString(pathStr); + fprintf(stderr, "setting i=%d, _bondedIdx[%d]=%d to bonded (%s %s)\n", i, i, _bondedIdx[i], getSlave(_paths[_bondedIdx[i]])->ifname().c_str(), pathStr); + } + } + _numBondedPaths = updatedBondedPathCount; + + if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_RR) { + // Cause a RR reset since the currently used index might no longer be valid + _rrPacketsSentOnCurrSlave = _packetsPerSlave; + } + } + } +} + +void Bond::estimatePathQuality(const int64_t now) +{ + char pathStr[128]; + //--- + + uint32_t totUserSpecifiedSlaveSpeed = 0; + if (_numBondedPaths) { // Compute relative user-specified speeds of slaves + for(unsigned int i=0;i<_numBondedPaths;++i) { + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + if (_paths[i] && _paths[i]->allowed()) { + totUserSpecifiedSlaveSpeed += slave->speed(); + } + } + for(unsigned int i=0;i<_numBondedPaths;++i) { + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + if (_paths[i] && _paths[i]->allowed()) { + slave->setRelativeSpeed(round( ((float)slave->speed() / (float)totUserSpecifiedSlaveSpeed) * 255)); + } + } + } + + float lat[ZT_MAX_PEER_NETWORK_PATHS]; + float pdv[ZT_MAX_PEER_NETWORK_PATHS]; + float plr[ZT_MAX_PEER_NETWORK_PATHS]; + float per[ZT_MAX_PEER_NETWORK_PATHS]; + float thr[ZT_MAX_PEER_NETWORK_PATHS]; + float thm[ZT_MAX_PEER_NETWORK_PATHS]; + float thv[ZT_MAX_PEER_NETWORK_PATHS]; + + float maxLAT = 0; + float maxPDV = 0; + float maxPLR = 0; + float maxPER = 0; + float maxTHR = 0; + float maxTHM = 0; + float maxTHV = 0; + + float quality[ZT_MAX_PEER_NETWORK_PATHS]; + uint8_t alloc[ZT_MAX_PEER_NETWORK_PATHS]; + + float totQuality = 0.0f; + + memset(&lat, 0, sizeof(lat)); + memset(&pdv, 0, sizeof(pdv)); + memset(&plr, 0, sizeof(plr)); + memset(&per, 0, sizeof(per)); + memset(&thr, 0, sizeof(thr)); + memset(&thm, 0, sizeof(thm)); + memset(&thv, 0, sizeof(thv)); + memset(&quality, 0, sizeof(quality)); + memset(&alloc, 0, sizeof(alloc)); + + // Compute initial summary statistics + for(unsigned int i=0;iallowed()) { + continue; + } + // Compute/Smooth average of real-world observations + _paths[i]->_latencyMean = _paths[i]->latencySamples.mean(); + _paths[i]->_latencyVariance = _paths[i]->latencySamples.stddev(); + _paths[i]->_packetErrorRatio = 1.0 - (_paths[i]->packetValiditySamples.count() ? _paths[i]->packetValiditySamples.mean() : 1.0); + + if (userHasSpecifiedSlaveSpeeds()) { + // Use user-reported metrics + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + if (slave) { + _paths[i]->_throughputMean = slave->speed(); + _paths[i]->_throughputVariance = 0; + } + } + /* + else { + // Use estimated metrics + if (_paths[i]->throughputSamples.count()) { + // If we have samples, use them + _paths[i]->throughputMean = (uint64_t)_paths[i]->throughputSamples.mean(); + if (_paths[i]->throughputMean > 0) { + _paths[i]->throughputVarianceSamples.push((float)_paths[i]->throughputSamples.stddev() / (float)_paths[i]->throughputMean); + _paths[i]->throughputVariance = _paths[i]->throughputVarianceSamples.mean(); + } + } + else { + // No samples have been collected yet, assume best case scenario + _paths[i]->throughputMean = ZT_QOS_THR_NORM_MAX; + _paths[i]->throughputVariance = 0; + } + } + */ + // Drain unacknowledged QoS records + std::map::iterator it = _paths[i]->qosStatsOut.begin(); + uint64_t currentLostRecords = 0; + while (it != _paths[i]->qosStatsOut.end()) { + int qosRecordTimeout = 5000; //_paths[i]->monitorInterval() * ZT_MULTIPATH_QOS_ACK_INTERVAL_MULTIPLIER * 8; + if ((now - it->second) >= qosRecordTimeout) { + //fprintf(stderr, "packetId=%llx was lost\n", it->first); + it = _paths[i]->qosStatsOut.erase(it); + ++currentLostRecords; + } else { ++it; } + } + + quality[i]=0; + totQuality=0; + // Normalize raw observations according to sane limits and/or user specified values + lat[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_latencyMean, 0, _maxAcceptableLatency, 0, 1)); + pdv[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_latencyVariance, 0, _maxAcceptablePacketDelayVariance, 0, 1)); + plr[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_packetLossRatio, 0, _maxAcceptablePacketLossRatio, 0, 1)); + per[i] = 1.0 / expf(4*Utils::normalize(_paths[i]->_packetErrorRatio, 0, _maxAcceptablePacketErrorRatio, 0, 1)); + //thr[i] = 1.0; //Utils::normalize(_paths[i]->throughputMean, 0, ZT_QOS_THR_NORM_MAX, 0, 1); + //thm[i] = 1.0; //Utils::normalize(_paths[i]->throughputMax, 0, ZT_QOS_THM_NORM_MAX, 0, 1); + //thv[i] = 1.0; //1.0 / expf(4*Utils::normalize(_paths[i]->throughputVariance, 0, ZT_QOS_THV_NORM_MAX, 0, 1)); + //scp[i] = _paths[i]->ipvPref != 0 ? 1.0 : Utils::normalize(_paths[i]->ipScope(), InetAddress::IP_SCOPE_NONE, InetAddress::IP_SCOPE_PRIVATE, 0, 1); + // Record bond-wide maximums to determine relative values + maxLAT = lat[i] > maxLAT ? lat[i] : maxLAT; + maxPDV = pdv[i] > maxPDV ? pdv[i] : maxPDV; + maxPLR = plr[i] > maxPLR ? plr[i] : maxPLR; + maxPER = per[i] > maxPER ? per[i] : maxPER; + //maxTHR = thr[i] > maxTHR ? thr[i] : maxTHR; + //maxTHM = thm[i] > maxTHM ? thm[i] : maxTHM; + //maxTHV = thv[i] > maxTHV ? thv[i] : maxTHV; + + //fprintf(stdout, "EH %d: lat=%8.3f, ltm=%8.3f, pdv=%8.3f, plr=%5.3f, per=%5.3f, thr=%8f, thm=%5.3f, thv=%5.3f, avl=%5.3f, age=%8.2f, scp=%4d, q=%5.3f, qtot=%5.3f, ac=%d if=%s, path=%s\n", + // i, lat[i], ltm[i], pdv[i], plr[i], per[i], thr[i], thm[i], thv[i], avl[i], age[i], scp[i], quality[i], totQuality, alloc[i], getSlave(_paths[i])->ifname().c_str(), pathStr); + + } + // Convert metrics to relative quantities and apply contribution weights + for(unsigned int i=0;ibonded()) { + quality[i] += ((maxLAT > 0.0f ? lat[i] / maxLAT : 0.0f) * _qualityWeights[ZT_QOS_LAT_IDX]); + quality[i] += ((maxPDV > 0.0f ? pdv[i] / maxPDV : 0.0f) * _qualityWeights[ZT_QOS_PDV_IDX]); + quality[i] += ((maxPLR > 0.0f ? plr[i] / maxPLR : 0.0f) * _qualityWeights[ZT_QOS_PLR_IDX]); + quality[i] += ((maxPER > 0.0f ? per[i] / maxPER : 0.0f) * _qualityWeights[ZT_QOS_PER_IDX]); + //quality[i] += ((maxTHR > 0.0f ? thr[i] / maxTHR : 0.0f) * _qualityWeights[ZT_QOS_THR_IDX]); + //quality[i] += ((maxTHM > 0.0f ? thm[i] / maxTHM : 0.0f) * _qualityWeights[ZT_QOS_THM_IDX]); + //quality[i] += ((maxTHV > 0.0f ? thv[i] / maxTHV : 0.0f) * _qualityWeights[ZT_QOS_THV_IDX]); + //quality[i] += (scp[i] * _qualityWeights[ZT_QOS_SCP_IDX]); + totQuality += quality[i]; + } + } + // + for(unsigned int i=0;ibonded()) { + alloc[i] = std::ceil((quality[i] / totQuality) * (float)255); + _paths[i]->_allocation = alloc[i]; + } + } + /* + if ((now - _lastLogTS) > 500) { + if (!relevant()) {return;} + //fprintf(stderr, "\n"); + _lastPrintTS = now; + _lastLogTS = now; + int numPlottablePaths=0; + for(unsigned int i=0;iaddress().toString(pathStr); + //fprintf(stderr, "%lu FIN [%d/%d]: pmi=%5d, lat=%4.3f, ltm=%4.3f, pdv=%4.3f, plr=%4.3f, per=%4.3f, thr=%4.3f, thm=%4.3f, thv=%4.3f, age=%4.3f, scp=%4d, q=%4.3f, qtot=%4.3f, ac=%4d, asf=%3d, if=%s, path=%20s, bond=%d, qosout=%d, plrraw=%d\n", + // ((now - RR->bc->getBondStartTime())), i, _numBondedPaths, _paths[i]->monitorInterval, + // lat[i], ltm[i], pdv[i], plr[i], per[i], thr[i], thm[i], thv[i], age[i], scp[i], + // quality[i], totQuality, alloc[i], _paths[i]->assignedFlowCount, getSlave(_paths[i])->ifname().c_str(), pathStr, _paths[i]->bonded(), _paths[i]->qosStatsOut.size(), _paths[i]->packetLossRatio); + } + } + if (numPlottablePaths < 2) { + return; + } + if (!_header) { + fprintf(stdout, "now, bonded, relativeUnderload, flows, "); + for(unsigned int i=0;iaddress().toString(pathStr); + std::string label = std::string((pathStr)) + " " + getSlave(_paths[i])->ifname(); + for (int i=0; i<19; ++i) { + fprintf(stdout, "%s, ", label.c_str()); + } + } + } + _header=true; + } + fprintf(stdout, "%ld, %d, %d, %d, ",((now - RR->bc->getBondStartTime())),_numBondedPaths,_totalBondUnderload, _flows.size()); + for(unsigned int i=0;iaddress().toString(pathStr); + fprintf(stdout, "%s, %s, %8.3f, %8.3f, %8.3f, %5.3f, %5.3f, %5.3f, %8f, %5.3f, %5.3f, %d, %5.3f, %d, %d, %d, %d, %d, %d, ", + getSlave(_paths[i])->ifname().c_str(), pathStr, _paths[i]->latencyMean, lat[i],pdv[i], _paths[i]->packetLossRatio, plr[i],per[i],thr[i],thm[i],thv[i],(now - _paths[i]->lastIn()),quality[i],alloc[i], + _paths[i]->relativeByteLoad, _paths[i]->assignedFlowCount, _paths[i]->alive(now, true), _paths[i]->eligible(now,_ackSendInterval), _paths[i]->qosStatsOut.size()); + } + } + fprintf(stdout, "\n"); + } + */ +} + +void Bond::processBalanceTasks(const int64_t now) +{ + // Omitted +} + +void Bond::dequeueNextActiveBackupPath(const uint64_t now) +{ + //fprintf(stderr, "dequeueNextActiveBackupPath\n"); + if (_abFailoverQueue.empty()) { + return; + } + _abPath = _abFailoverQueue.front(); + _abFailoverQueue.pop_front(); + _lastActiveBackupPathChange = now; + for (int i=0; iresetPacketCounts(); + } + } +} + +void Bond::processActiveBackupTasks(const int64_t now) +{ + //fprintf(stderr, "%llu processActiveBackupTasks\n", (now - RR->bc->getBondStartTime())); + char pathStr[128]; char prevPathStr[128]; char curPathStr[128]; + + SharedPtr prevActiveBackupPath = _abPath; + SharedPtr nonPreferredPath; + bool bFoundPrimarySlave = false; + + /** + * Select initial "active" active-backup slave + */ + if (!_abPath) { + fprintf(stderr, "%llu no active backup path yet...\n", ((now - RR->bc->getBondStartTime()))); + /** + * [Automatic mode] + * The user has not explicitly specified slaves or their failover schedule, + * the bonding policy will now select the first eligible path and set it as + * its active backup path, if a substantially better path is detected the bonding + * policy will assign it as the new active backup path. If the path fails it will + * simply find the next eligible path. + */ + if (!userHasSpecifiedSlaves()) { + fprintf(stderr, "%llu AB: (auto) user did not specify any slaves. waiting until we know more\n", ((now - RR->bc->getBondStartTime()))); + for (int i=0; ieligible(now,_ackSendInterval)) { + _paths[i]->address().toString(curPathStr); + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + if (slave) { + fprintf(stderr, "%llu AB: (initial) [%d] found eligible path %s on: %s\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, slave->ifname().c_str()); + } + _abPath = _paths[i]; + break; + } + } + } + /** + * [Manual mode] + * The user has specified slaves or failover rules that the bonding policy should adhere to. + */ + else if (userHasSpecifiedSlaves()) { + fprintf(stderr, "%llu AB: (manual) no active backup slave, checking local.conf\n", ((now - RR->bc->getBondStartTime()))); + if (userHasSpecifiedPrimarySlave()) { + fprintf(stderr, "%llu AB: (manual) user has specified primary slave, looking for it.\n", ((now - RR->bc->getBondStartTime()))); + for (int i=0; i slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + if (_paths[i]->eligible(now,_ackSendInterval) && slave->primary()) { + if (!_paths[i]->preferred()) { + _paths[i]->address().toString(curPathStr); + fprintf(stderr, "%llu AB: (initial) [%d] found path on primary slave, taking note in case we don't find a preferred path\n", ((now - RR->bc->getBondStartTime())), i); + nonPreferredPath = _paths[i]; + bFoundPrimarySlave = true; + } + if (_paths[i]->preferred()) { + _abPath = _paths[i]; + _abPath->address().toString(curPathStr); + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + if (slave) { + fprintf(stderr, "%llu AB: (initial) [%d] found preferred path %s on primary slave: %s\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, slave->ifname().c_str()); + } + bFoundPrimarySlave = true; + break; + } + } + } + if (_abPath) { + _abPath->address().toString(curPathStr); + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _abPath->localSocket()); + if (slave) { + fprintf(stderr, "%llu AB: (initial) found preferred primary path: %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, slave->ifname().c_str()); + } + } + else { + if (bFoundPrimarySlave && nonPreferredPath) { + fprintf(stderr, "%llu AB: (initial) found a non-preferred primary path\n", ((now - RR->bc->getBondStartTime()))); + _abPath = nonPreferredPath; + } + } + if (!_abPath) { + fprintf(stderr, "%llu AB: (initial) designated primary slave is not yet ready\n", ((now - RR->bc->getBondStartTime()))); + // TODO: Should fail-over to specified backup or just wait? + } + } + else if (!userHasSpecifiedPrimarySlave()) { + int _abIdx = ZT_MAX_PEER_NETWORK_PATHS; + fprintf(stderr, "%llu AB: (initial) user did not specify primary slave, just picking something\n", ((now - RR->bc->getBondStartTime()))); + for (int i=0; ieligible(now,_ackSendInterval)) { + _abIdx = i; + break; + } + } + if (_abIdx == ZT_MAX_PEER_NETWORK_PATHS) { + fprintf(stderr, "%llu AB: (initial) unable to find a candidate next-best, no change\n", ((now - RR->bc->getBondStartTime()))); + } + else { + _abPath = _paths[_abIdx]; + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _abPath->localSocket()); + if (slave) { + fprintf(stderr, "%llu AB: (initial) selected non-primary slave idx=%d, %s on %s\n", ((now - RR->bc->getBondStartTime())), _abIdx, pathStr, slave->ifname().c_str()); + } + } + } + } + } + /** + * Update and maintain the active-backup failover queue + */ + if (_abPath) { + // Don't worry about the failover queue until we have an active slave + // Remove ineligible paths from the failover slave queue + for (std::list >::iterator it(_abFailoverQueue.begin()); it!=_abFailoverQueue.end();) { + if ((*it) && !(*it)->eligible(now,_ackSendInterval)) { + (*it)->address().toString(curPathStr); + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, (*it)->localSocket()); + if (slave) { + fprintf(stderr, "%llu AB: (fq) %s on %s is now ineligible, removing from failover queue\n", ((now - RR->bc->getBondStartTime())), curPathStr, slave->ifname().c_str()); + } + it = _abFailoverQueue.erase(it); + } else { + ++it; + } + } + /** + * Failover instructions were provided by user, build queue according those as well as IPv + * preference, disregarding performance. + */ + if (userHasSpecifiedFailoverInstructions()) { + /** + * Clear failover scores + */ + for (int i=0; i_failoverScore = 0; + } + } + //fprintf(stderr, "AB: (fq) user has specified specific failover instructions, will follow them.\n"); + for (int i=0; iallowed() || !_paths[i]->eligible(now,_ackSendInterval)) { + continue; + } + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + _paths[i]->address().toString(pathStr); + + int failoverScoreHandicap = _paths[i]->_failoverScore; + if (_paths[i]->preferred()) + { + failoverScoreHandicap += ZT_MULTIPATH_FAILOVER_HANDICAP_PREFERRED; + //fprintf(stderr, "%s on %s ----> %d for preferred\n", pathStr, _paths[i]->ifname().c_str(), failoverScoreHandicap); + } + if (slave->primary()) { + // If using "optimize" primary reselect mode, ignore user slave designations + failoverScoreHandicap += ZT_MULTIPATH_FAILOVER_HANDICAP_PRIMARY; + //fprintf(stderr, "%s on %s ----> %d for primary\n", pathStr, _paths[i]->ifname().c_str(), failoverScoreHandicap); + } + if (!_paths[i]->_failoverScore) { + // If we didn't inherit a failover score from a "parent" that wants to use this path as a failover + int newHandicap = failoverScoreHandicap ? failoverScoreHandicap : _paths[i]->_allocation; + _paths[i]->_failoverScore = newHandicap; + //fprintf(stderr, "%s on %s ----> %d for allocation\n", pathStr, _paths[i]->ifname().c_str(), newHandicap); + } + SharedPtr failoverSlave; + if (slave->failoverToSlave().length()) { + failoverSlave = RR->bc->getSlaveByName(_policyAlias, slave->failoverToSlave()); + } + if (failoverSlave) { + for (int j=0; jaddress().toString(pathStr); + int inheritedHandicap = failoverScoreHandicap - 10; + int newHandicap = _paths[j]->_failoverScore > inheritedHandicap ? _paths[j]->_failoverScore : inheritedHandicap; + //fprintf(stderr, "\thanding down %s on %s ----> %d\n", pathStr, getSlave(_paths[j])->ifname().c_str(), newHandicap); + if (!_paths[j]->preferred()) { + newHandicap--; + } + _paths[j]->_failoverScore = newHandicap; + } + } + } + if (_paths[i].ptr() != _abPath.ptr()) { + bool bFoundPathInQueue = false; + for (std::list >::iterator it(_abFailoverQueue.begin()); it!=_abFailoverQueue.end();++it) { + if (_paths[i].ptr() == (*it).ptr()) { + bFoundPathInQueue = true; + } + } + if (!bFoundPathInQueue) { + _paths[i]->address().toString(curPathStr); + fprintf(stderr, "%llu AB: (fq) [%d] added %s on %s to queue\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, getSlave(_paths[i])->ifname().c_str()); + _abFailoverQueue.push_front(_paths[i]); + } + } + } + } + /** + * No failover instructions provided by user, build queue according to performance + * and IPv preference. + */ + else if (!userHasSpecifiedFailoverInstructions()) { + for (int i=0; iallowed() + || !_paths[i]->eligible(now,_ackSendInterval)) { + continue; + } + int failoverScoreHandicap = 0; + if (_paths[i]->preferred()) { + failoverScoreHandicap = ZT_MULTIPATH_FAILOVER_HANDICAP_PREFERRED; + } + bool includeRefractoryPeriod = true; + if (!_paths[i]->eligible(now,includeRefractoryPeriod)) { + failoverScoreHandicap = -10000; + } + if (getSlave(_paths[i])->primary() && _abSlaveSelectMethod != ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE) { + // If using "optimize" primary reselect mode, ignore user slave designations + failoverScoreHandicap = ZT_MULTIPATH_FAILOVER_HANDICAP_PRIMARY; + } + if (_paths[i].ptr() == negotiatedPath.ptr()) { + _paths[i]->_negotiated = true; + failoverScoreHandicap = ZT_MULTIPATH_FAILOVER_HANDICAP_NEGOTIATED; + } else { + _paths[i]->_negotiated = false; + } + _paths[i]->_failoverScore = _paths[i]->_allocation + failoverScoreHandicap; + if (_paths[i].ptr() != _abPath.ptr()) { + bool bFoundPathInQueue = false; + for (std::list >::iterator it(_abFailoverQueue.begin()); it!=_abFailoverQueue.end();++it) { + if (_paths[i].ptr() == (*it).ptr()) { + bFoundPathInQueue = true; + } + } + if (!bFoundPathInQueue) { + _paths[i]->address().toString(curPathStr); + fprintf(stderr, "%llu AB: (fq) [%d] added %s on %s to queue\n", ((now - RR->bc->getBondStartTime())), i, curPathStr, getSlave(_paths[i])->ifname().c_str()); + _abFailoverQueue.push_front(_paths[i]); + } + } + } + } + _abFailoverQueue.sort(PathQualityComparator()); + if (_abFailoverQueue.empty()) { + fprintf(stderr, "%llu AB: (fq) the failover queue is empty, the active-backup bond is no longer fault-tolerant\n", ((now - RR->bc->getBondStartTime()))); + } + } + /** + * Short-circuit if we have no queued paths + */ + if (_abFailoverQueue.empty()) { + return; + } + /** + * Fulfill primary reselect obligations + */ + if (_abPath && !_abPath->eligible(now,_ackSendInterval)) { // Implicit ZT_MULTIPATH_RESELECTION_POLICY_FAILURE + _abPath->address().toString(curPathStr); fprintf(stderr, "%llu AB: (failure) failover event!, active backup path (%s) is no-longer eligible\n", ((now - RR->bc->getBondStartTime())), curPathStr); + if (!_abFailoverQueue.empty()) { + fprintf(stderr, "%llu AB: (failure) there are (%lu) slaves in queue to choose from...\n", ((now - RR->bc->getBondStartTime())), _abFailoverQueue.size()); + dequeueNextActiveBackupPath(now); + _abPath->address().toString(curPathStr); fprintf(stderr, "%llu sAB: (failure) switched to %s on %s\n", ((now - RR->bc->getBondStartTime())), curPathStr, getSlave(_abPath)->ifname().c_str()); + } else { + fprintf(stderr, "%llu AB: (failure) nothing available in the slave queue, doing nothing.\n", ((now - RR->bc->getBondStartTime()))); + } + } + /** + * Detect change to prevent flopping during later optimization step. + */ + if (prevActiveBackupPath != _abPath) { + _lastActiveBackupPathChange = now; + } + if (_abSlaveSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_ALWAYS) { + if (_abPath && !getSlave(_abPath)->primary() + && getSlave(_abFailoverQueue.front())->primary()) { + fprintf(stderr, "%llu AB: (always) switching to available primary\n", ((now - RR->bc->getBondStartTime()))); + dequeueNextActiveBackupPath(now); + } + } + if (_abSlaveSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_BETTER) { + if (_abPath && !getSlave(_abPath)->primary()) { + fprintf(stderr, "%llu AB: (better) active backup has switched to \"better\" primary slave according to re-select policy.\n", ((now - RR->bc->getBondStartTime()))); + if (getSlave(_abFailoverQueue.front())->primary() + && (_abFailoverQueue.front()->_failoverScore > _abPath->_failoverScore)) { + dequeueNextActiveBackupPath(now); + fprintf(stderr, "%llu AB: (better) switched back to user-defined primary\n", ((now - RR->bc->getBondStartTime()))); + } + } + } + if (_abSlaveSelectMethod == ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE && !_abFailoverQueue.empty()) { + /** + * Implement link negotiation that was previously-decided + */ + if (_abFailoverQueue.front()->_negotiated) { + dequeueNextActiveBackupPath(now); + _abPath->address().toString(prevPathStr); + fprintf(stderr, "%llu AB: (optimize) switched to negotiated path %s on %s\n", ((now - RR->bc->getBondStartTime())), prevPathStr, getSlave(_abPath)->ifname().c_str()); + _lastPathNegotiationCheck = now; + } + else { + // Try to find a better path and automatically switch to it -- not too often, though. + if ((now - _lastActiveBackupPathChange) > ZT_MULTIPATH_MIN_ACTIVE_BACKUP_AUTOFLOP_INTERVAL) { + if (!_abFailoverQueue.empty()) { + //fprintf(stderr, "AB: (optimize) there are (%d) slaves in queue to choose from...\n", _abFailoverQueue.size()); + int newFScore = _abFailoverQueue.front()->_failoverScore; + int prevFScore = _abPath->_failoverScore; + // Establish a minimum switch threshold to prevent flapping + int failoverScoreDifference = _abFailoverQueue.front()->_failoverScore - _abPath->_failoverScore; + int thresholdQuantity = (ZT_MULTIPATH_ACTIVE_BACKUP_OPTIMIZE_MIN_THRESHOLD * (float)_abPath->_allocation); + if ((failoverScoreDifference > 0) && (failoverScoreDifference > thresholdQuantity)) { + SharedPtr oldPath = _abPath; + _abPath->address().toString(prevPathStr); + dequeueNextActiveBackupPath(now); + _abPath->address().toString(curPathStr); + fprintf(stderr, "%llu AB: (optimize) switched from %s on %s (fs=%d) to %s on %s (fs=%d)\n", ((now - RR->bc->getBondStartTime())), prevPathStr, getSlave(oldPath)->ifname().c_str(), prevFScore, curPathStr, getSlave(_abPath)->ifname().c_str(), newFScore); + } + } + } + } + } +} + +void Bond::setReasonableDefaults(int policy) +{ + // If invalid bonding policy, try default + int _defaultBondingPolicy = BondController::defaultBondingPolicy(); + if (policy <= ZT_BONDING_POLICY_NONE || policy > ZT_BONDING_POLICY_BALANCE_AWARE) { + // If no default set, use NONE (effectively disabling this bond) + if (_defaultBondingPolicy < ZT_BONDING_POLICY_NONE || _defaultBondingPolicy > ZT_BONDING_POLICY_BALANCE_AWARE) { + _bondingPolicy= ZT_BONDING_POLICY_NONE; + } + _bondingPolicy= _defaultBondingPolicy; + } else { + _bondingPolicy= policy; + } + + _downDelay = 0; + _upDelay = 0; + _allowFlowHashing=false; + _bondMonitorInterval=0; + _allowPathNegotiation=false; + _shouldCollectPathStatistics=false; + _lastPathNegotiationReceived=0; + _lastBackgroundTaskCheck=0; + _lastPathNegotiationCheck=0; + + _lastFlowStatReset=0; + _lastFlowExpirationCheck=0; + _localUtility=0; + _numBondedPaths=0; + _rrPacketsSentOnCurrSlave=0; + _rrIdx=0; + _lastPathNegotiationReceived=0; + _pathNegotiationCutoffCount=0; + _lastFlowRebalance=0; + _totalBondUnderload = 0; + + //_maxAcceptableLatency + _maxAcceptablePacketDelayVariance = 50; + _maxAcceptablePacketLossRatio = 0.10; + _maxAcceptablePacketErrorRatio = 0.10; + _userHasSpecifiedSlaveSpeeds=0; + + _lastFrame=0; + + // TODO: Remove + _header=false; + _lastLogTS = 0; + _lastPrintTS = 0; + + + + + /** + * Paths are actively monitored to provide a real-time quality/preference-ordered rapid failover queue. + */ + switch (policy) { + case ZT_BONDING_POLICY_ACTIVE_BACKUP: + _failoverInterval = 5000; + _abSlaveSelectMethod = ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE; + _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; + _qualityWeights[ZT_QOS_LAT_IDX] = 0.2f; + _qualityWeights[ZT_QOS_LTM_IDX] = 0.0f; + _qualityWeights[ZT_QOS_PDV_IDX] = 0.2f; + _qualityWeights[ZT_QOS_PLR_IDX] = 0.2f; + _qualityWeights[ZT_QOS_PER_IDX] = 0.2f; + _qualityWeights[ZT_QOS_THR_IDX] = 0.2f; + _qualityWeights[ZT_QOS_THM_IDX] = 0.0f; + _qualityWeights[ZT_QOS_THV_IDX] = 0.0f; + _qualityWeights[ZT_QOS_SCP_IDX] = 0.0f; + break; + /** + * All seemingly-alive paths are used. Paths are not actively monitored. + */ + case ZT_BONDING_POLICY_BROADCAST: + _downDelay = 30000; + _upDelay = 0; + break; + /** + * Paths are monitored to determine when/if one needs to be added or removed from the rotation + */ + case ZT_BONDING_POLICY_BALANCE_RR: + _failoverInterval = 5000; + _allowFlowHashing = false; + _packetsPerSlave = 8; + _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; + _qualityWeights[ZT_QOS_LAT_IDX] = 0.4f; + _qualityWeights[ZT_QOS_LTM_IDX] = 0.0f; + _qualityWeights[ZT_QOS_PDV_IDX] = 0.2f; + _qualityWeights[ZT_QOS_PLR_IDX] = 0.1f; + _qualityWeights[ZT_QOS_PER_IDX] = 0.1f; + _qualityWeights[ZT_QOS_THR_IDX] = 0.1f; + _qualityWeights[ZT_QOS_THM_IDX] = 0.0f; + _qualityWeights[ZT_QOS_THV_IDX] = 0.0f; + _qualityWeights[ZT_QOS_SCP_IDX] = 0.0f; + break; + /** + * Path monitoring is used to determine the capacity of each + * path and where to place the next flow. + */ + case ZT_BONDING_POLICY_BALANCE_XOR: + _failoverInterval = 5000;; + _upDelay=_bondMonitorInterval*2; + _allowFlowHashing = true; + _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; + _qualityWeights[ZT_QOS_LAT_IDX] = 0.4f; + _qualityWeights[ZT_QOS_LTM_IDX] = 0.0f; + _qualityWeights[ZT_QOS_PDV_IDX] = 0.2f; + _qualityWeights[ZT_QOS_PLR_IDX] = 0.1f; + _qualityWeights[ZT_QOS_PER_IDX] = 0.1f; + _qualityWeights[ZT_QOS_THR_IDX] = 0.1f; + _qualityWeights[ZT_QOS_THM_IDX] = 0.0f; + _qualityWeights[ZT_QOS_THV_IDX] = 0.0f; + _qualityWeights[ZT_QOS_SCP_IDX] = 0.0f; + break; + /** + * Path monitoring is used to determine the capacity of each + * path and where to place the next flow. Additionally, re-shuffling + * of flows may take place. + */ + case ZT_BONDING_POLICY_BALANCE_AWARE: + _failoverInterval = 3000; + _allowFlowHashing = true; + _slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC; + _qualityWeights[ZT_QOS_LAT_IDX] = 0.3f; + _qualityWeights[ZT_QOS_LTM_IDX] = 0.0f; + _qualityWeights[ZT_QOS_PDV_IDX] = 0.1f; + _qualityWeights[ZT_QOS_PLR_IDX] = 0.1f; + _qualityWeights[ZT_QOS_PER_IDX] = 0.1f; + _qualityWeights[ZT_QOS_THR_IDX] = 0.0f; + _qualityWeights[ZT_QOS_THM_IDX] = 0.4f; + _qualityWeights[ZT_QOS_THV_IDX] = 0.0f; + _qualityWeights[ZT_QOS_SCP_IDX] = 0.0f; + break; + default: + break; + } + + /** + * Timer geometries and counters + */ + _bondMonitorInterval = _failoverInterval / 3; + _ackSendInterval = _failoverInterval; + _qualityEstimationInterval = _failoverInterval * 2; + + _dynamicPathMonitorInterval = 0; + + _downDelay=0; + _upDelay=0; + + _ackCutoffCount = 0; + _lastAckRateCheck = 0; + _qosSendInterval = _bondMonitorInterval * 4; + _qosCutoffCount = 0; + _lastQoSRateCheck = 0; + throughputMeasurementInterval = _ackSendInterval * 2; + BondController::setMinReqPathMonitorInterval(_bondMonitorInterval); + + _defaultPathRefractoryPeriod = 8000; + + fprintf(stderr, "TIMERS: strat=%d, fi= %d, bmi= %d, qos= %d, ack= %d, estimateInt= %d, refractory= %d, ud= %d, dd= %d\n", + _slaveMonitorStrategy, + _failoverInterval, + _bondMonitorInterval, + _qosSendInterval, + _ackSendInterval, + _qualityEstimationInterval, + _defaultPathRefractoryPeriod, + _upDelay, + _downDelay); + + _lastQualityEstimation=0; +} + +void Bond::setUserQualityWeights(float weights[], int len) +{ + if (len == ZT_QOS_WEIGHT_SIZE) { + float weightTotal = 0.0; + for (unsigned int i=0; i 0.99 && weightTotal < 1.01) { + memcpy(_qualityWeights, weights, len * sizeof(float)); + } + } +} + + +bool Bond::relevant() { + return _peer->identity().address().toInt() == 0x16a03a3d03 + || _peer->identity().address().toInt() == 0x4410300d03 + || _peer->identity().address().toInt() == 0x795cbf86fa; +} + +SharedPtr Bond::getSlave(const SharedPtr& path) +{ + return RR->bc->getSlaveBySocket(_policyAlias, path->localSocket()); +} + +void Bond::dumpInfo(const int64_t now) +{ + char pathStr[128]; + //char oldPathStr[128]; + char currPathStr[128]; + + if (!relevant()) { + return; + } + /* + fprintf(stderr, "---[ bp=%d, id=%llx, dd=%d, up=%d, pmi=%d, specifiedSlaves=%d, _specifiedPrimarySlave=%d, _specifiedFailInst=%d ]\n", + _policy, _peer->identity().address().toInt(), _downDelay, _upDelay, _monitorInterval, _userHasSpecifiedSlaves, _userHasSpecifiedPrimarySlave, _userHasSpecifiedFailoverInstructions); + + if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP) { + fprintf(stderr, "Paths (bp=%d, stats=%d, primaryReselect=%d) :\n", + _policy, _shouldCollectPathStatistics, _abSlaveSelectMethod); + } + if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_RR + || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_XOR + || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE) { + fprintf(stderr, "Paths (bp=%d, stats=%d, fh=%d) :\n", + _policy, _shouldCollectPathStatistics, _allowFlowHashing); + }*/ + + if ((now - _lastLogTS) < 1000) { + return; + } + _lastPrintTS = now; + _lastLogTS = now; + + fprintf(stderr, "\n\n"); + + for(int i=0; i slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[i]->localSocket()); + _paths[i]->address().toString(pathStr); + fprintf(stderr, " %2d: lat=%8.3f, ac=%3d, fail%5s, fscore=%6d, in=%7d, out=%7d, age=%7ld, ack=%7ld, ref=%6d, ls=%llx", + i, + _paths[i]->_latencyMean, + _paths[i]->_allocation, + slave->failoverToSlave().c_str(), + _paths[i]->_failoverScore, + _paths[i]->_packetsIn, + _paths[i]->_packetsOut, + (long)_paths[i]->age(now), + (long)_paths[i]->ackAge(now), + _paths[i]->_refractoryPeriod, + _paths[i]->localSocket() + ); + if (slave->spare()) { + fprintf(stderr, " SPR."); + } else { + fprintf(stderr, " "); + } + if (slave->primary()) { + fprintf(stderr, " PRIM."); + } else { + fprintf(stderr, " "); + } + if (_paths[i]->allowed()) { + fprintf(stderr, " ALL."); + } else { + fprintf(stderr, " "); + } + if (_paths[i]->eligible(now,_ackSendInterval)) { + fprintf(stderr, " ELI."); + } else { + fprintf(stderr, " "); + } + if (_paths[i]->preferred()) { + fprintf(stderr, " PREF."); + } else { + fprintf(stderr, " "); + } + if (_paths[i]->_negotiated) { + fprintf(stderr, " NEG."); + } else { + fprintf(stderr, " "); + } + if (_paths[i]->bonded()) { + fprintf(stderr, " BOND "); + } else { + fprintf(stderr, " "); + } + if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP && _abPath && (_abPath == _paths[i].ptr())) { + fprintf(stderr, " ACTIVE "); + } else if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP) { + fprintf(stderr, " "); + } + if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP && _abFailoverQueue.size() && (_abFailoverQueue.front().ptr() == _paths[i].ptr())) { + fprintf(stderr, " NEXT "); + } else if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP) { + fprintf(stderr, " "); + } + fprintf(stderr, "%5s %s\n", slave->ifname().c_str(), pathStr); + } + } + + if (_bondingPolicy== ZT_BONDING_POLICY_ACTIVE_BACKUP) { + if (!_abFailoverQueue.empty()) { + fprintf(stderr, "\nFailover Queue:\n"); + for (std::list >::iterator it(_abFailoverQueue.begin()); it!=_abFailoverQueue.end();++it) { + (*it)->address().toString(currPathStr); + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, (*it)->localSocket()); + fprintf(stderr, "\t%8s\tspeed=%7d\trelSpeed=%3d\tipvPref=%3d\tfscore=%9d\t\t%s\n", + slave->ifname().c_str(), + slave->speed(), + slave->relativeSpeed(), + slave->ipvPref(), + (*it)->_failoverScore, + currPathStr); + } + } + else + { + fprintf(stderr, "\nFailover Queue size = %lu\n", _abFailoverQueue.size()); + } + } + + if (_bondingPolicy== ZT_BONDING_POLICY_BALANCE_RR + || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_XOR + || _bondingPolicy== ZT_BONDING_POLICY_BALANCE_AWARE) { + /* + if (_numBondedPaths) { + fprintf(stderr, "\nBonded Paths:\n"); + for (int i=0; i<_numBondedPaths; ++i) { + _paths[_bondedIdx[i]].p->address().toString(currPathStr); + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, _paths[_bondedIdx[i]].p->localSocket()); + fprintf(stderr, " [%d]\t%8s\tflows=%3d\tspeed=%7d\trelSpeed=%3d\tipvPref=%3d\tfscore=%9d\t\t%s\n", i, + //fprintf(stderr, " [%d]\t%8s\tspeed=%7d\trelSpeed=%3d\tflowCount=%2d\tipvPref=%3d\tfscore=%9d\t\t%s\n", i, + slave->ifname().c_str(), + numberOfAssignedFlows(_paths[_bondedIdx[i]].p), + slave->speed(), + slave->relativeSpeed(), + //_paths[_bondedIdx[i]].p->assignedFlows.size(), + slave->ipvPref(), + _paths[_bondedIdx[i]].p->failoverScore(), + currPathStr); + } + } + */ + /* + if (_allowFlowHashing) { + //Mutex::Lock _l(_flows_m); + if (_flows.size()) { + fprintf(stderr, "\nFlows:\n"); + std::map >::iterator it = _flows.begin(); + while (it != _flows.end()) { + it->second->assignedPath()->address().toString(currPathStr); + SharedPtr slave =RR->bc->getSlaveBySocket(_policyAlias, it->second->assignedPath()->localSocket()); + fprintf(stderr, " [%4x] in=%16llu, out=%16llu, bytes=%16llu, last=%16llu, if=%8s\t\t%s\n", + it->second->id(), + it->second->bytesInPerUnitTime(), + it->second->bytesOutPerUnitTime(), + it->second->totalBytes(), + it->second->age(now), + slave->ifname().c_str(), + currPathStr); + ++it; + } + } + } + */ + } + //fprintf(stderr, "\n\n\n\n\n"); +} + +} // namespace ZeroTier \ No newline at end of file diff --git a/node/Bond.hpp b/node/Bond.hpp new file mode 100644 index 000000000..6318f3936 --- /dev/null +++ b/node/Bond.hpp @@ -0,0 +1,689 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2024-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +#ifndef ZT_BOND_HPP +#define ZT_BOND_HPP + +#include + +#include "Path.hpp" +#include "Peer.hpp" +#include "../osdep/Slave.hpp" +#include "Flow.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; +class Slave; + +class Bond +{ + friend class SharedPtr; + friend class Peer; + friend class BondController; + + struct PathQualityComparator + { + bool operator ()(const SharedPtr & a, const SharedPtr & b) + { + if(a->_failoverScore == b->_failoverScore) { + return a < b; + } + return a->_failoverScore > b->_failoverScore; + } + }; + +public: + + // TODO: Remove + bool _header; + int64_t _lastLogTS; + int64_t _lastPrintTS; + void dumpInfo(const int64_t now); + bool relevant(); + + SharedPtr getSlave(const SharedPtr& path); + + /** + * Constructor. For use only in first initialization in Node + * + * @param renv Runtime environment + */ + Bond(const RuntimeEnvironment *renv); + + /** + * Constructor. Creates a bond based off of ZT defaults + * + * @param renv Runtime environment + * @param policy Bonding policy + * @param peer + */ + Bond(const RuntimeEnvironment *renv, int policy, const SharedPtr& peer); + + /** + * Constructor. For use when user intends to manually specify parameters + * + * @param basePolicy + * @param policyAlias + * @param peer + */ + Bond(std::string& basePolicy, std::string& policyAlias, const SharedPtr& peer); + + /** + * Constructor. Creates a bond based off of a user-defined bond template + * + * @param renv Runtime environment + * @param original + * @param peer + */ + Bond(const RuntimeEnvironment *renv, const Bond &original, const SharedPtr& peer); + + /** + * + * @return + */ + std::string policyAlias() { return _policyAlias; } + + /** + * Inform the bond about the path that its peer just learned about + * + * @param path Newly-learned Path which should now be handled by the Bond + * @param now Current time + */ + void nominatePath(const SharedPtr& path, int64_t now); + + /** + * Propagate and memoize often-used bonding preferences for each path + */ + void applyUserPrefs(); + + /** + * Check path states and perform bond rebuilds if needed. + * + * @param now Current time + * @param rebuild Whether or not the bond should be reconstructed. + */ + void curateBond(const int64_t now, bool rebuild); + + /** + * Periodically perform statistical summaries of quality metrics for all paths. + * + * @param now Current time + */ + void estimatePathQuality(int64_t now); + + /** + * Record an invalid incoming packet. This packet failed + * MAC/compression/cipher checks and will now contribute to a + * Packet Error Ratio (PER). + * + * @param path Path over which packet was received + */ + void recordIncomingInvalidPacket(const SharedPtr& path); + + /** + * Record statistics on outbound an packet. + * + * @param path Path over which packet is being sent + * @param packetId Packet ID + * @param payloadLength Packet data length + * @param verb Packet verb + * @param flowId Flow ID + * @param now Current time + */ + void recordOutgoingPacket(const SharedPtr &path, uint64_t packetId, + uint16_t payloadLength, Packet::Verb verb, int32_t flowId, int64_t now); + + /** + * Process the contents of an inbound VERB_QOS_MEASUREMENT to gather path quality observations. + * + * @param now Current time + * @param count Number of records + * @param rx_id table of packet IDs + * @param rx_ts table of holding times + */ + void receivedQoS(const SharedPtr& path, int64_t now, int count, uint64_t *rx_id, uint16_t *rx_ts); + + /** + * Process the contents of an inbound VERB_ACK to gather path quality observations. + * + * @param path Path over which packet was received + * @param now Current time + * @param ackedBytes Number of bytes ACKed by this VERB_ACK + */ + void receivedAck(const SharedPtr& path, int64_t now, int32_t ackedBytes); + + /** + * Generate the contents of a VERB_QOS_MEASUREMENT packet. + * + * @param now Current time + * @param qosBuffer destination buffer + * @return Size of payload + */ + int32_t generateQoSPacket(const SharedPtr& path, int64_t now, char *qosBuffer); + + /** + * Record statistics for an inbound packet. + * + * @param path Path over which packet was received + * @param packetId Packet ID + * @param payloadLength Packet data length + * @param verb Packet verb + * @param flowId Flow ID + * @param now Current time + */ + void recordIncomingPacket(const SharedPtr& path, uint64_t packetId, uint16_t payloadLength, + Packet::Verb verb, int32_t flowId, int64_t now); + + /** + * Determines the most appropriate path for packet and flow egress. This decision is made by + * the underlying bonding policy as well as QoS-related statistical observations of path quality. + * + * @param now Current time + * @param flowId Flow ID + * @return Pointer to suggested Path + */ + SharedPtr getAppropriatePath(int64_t now, int32_t flowId); + + /** + * Creates a new flow record + * + * @param path Path over which flow shall be handled + * @param flowId Flow ID + * @param entropy A byte of entropy to be used by the bonding algorithm + * @param now Current time + * @return Pointer to newly-created Flow + */ + SharedPtr createFlow(const SharedPtr &path, int32_t flowId, unsigned char entropy, int64_t now); + + /** + * Removes flow records that are past a certain age limit. + * + * @param age Age threshold to be forgotten + * @param oldest Whether only the oldest shall be forgotten + * @param now Current time + */ + void forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now); + + /** + * Assigns a new flow to a bonded path + * + * @param flow Flow to be assigned + * @param now Current time + */ + bool assignFlowToBondedPath(SharedPtr &flow, int64_t now); + + /** + * Determine whether a path change should occur given the remote peer's reported utility and our + * local peer's known utility. This has the effect of assigning inbound and outbound traffic to + * the same path. + * + * @param now Current time + * @param path Path over which the negotiation request was received + * @param remoteUtility How much utility the remote peer claims to gain by using the declared path + */ + void processIncomingPathNegotiationRequest(uint64_t now, SharedPtr &path, int16_t remoteUtility); + + /** + * Determine state of path synchronization and whether a negotiation request + * shall be sent to the peer. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param now Current time + */ + void pathNegotiationCheck(void *tPtr, const int64_t now); + + /** + * Sends a VERB_ACK to the remote peer. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param path Path over which packet should be sent + * @param localSocket Local source socket + * @param atAddress + * @param now Current time + */ + void sendACK(void *tPtr,const SharedPtr &path,int64_t localSocket, + const InetAddress &atAddress,int64_t now); + + /** + * Sends a VERB_QOS_MEASUREMENT to the remote peer. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param path Path over which packet should be sent + * @param localSocket Local source socket + * @param atAddress + * @param now Current time + */ + void sendQOS_MEASUREMENT(void *tPtr,const SharedPtr &path,int64_t localSocket, + const InetAddress &atAddress,int64_t now); + + /** + * Sends a VERB_PATH_NEGOTIATION_REQUEST to the remote peer. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param path Path over which packet should be sent + */ + void sendPATH_NEGOTIATION_REQUEST(void *tPtr, const SharedPtr &path); + + /** + * + * @param now Current time + */ + void processBalanceTasks(int64_t now); + + /** + * Perform periodic tasks unique to active-backup + * + * @param now Current time + */ + void processActiveBackupTasks(int64_t now); + + /** + * Switches the active slave in an active-backup scenario to the next best during + * a failover event. + * + * @param now Current time + */ + void dequeueNextActiveBackupPath(uint64_t now); + + /** + * Set bond parameters to reasonable defaults, these may later be overwritten by + * user-specified parameters. + * + * @param policy Bonding policy + */ + void setReasonableDefaults(int policy); + + /** + * Check and assign user-specified quality weights to this bond. + * + * @param weights Set of user-specified weights + * @param len Length of weight vector + */ + void setUserQualityWeights(float weights[], int len); + + /** + * @param latencyInMilliseconds Maximum acceptable latency. + */ + void setMaxAcceptableLatency(int16_t latencyInMilliseconds) { + _maxAcceptableLatency = latencyInMilliseconds; + } + + /** + * @param latencyInMilliseconds Maximum acceptable (mean) latency. + */ + void setMaxAcceptableMeanLatency(int16_t latencyInMilliseconds) { + _maxAcceptableMeanLatency = latencyInMilliseconds; + } + + /** + * @param latencyVarianceInMilliseconds Maximum acceptable packet delay variance (jitter). + */ + void setMaxAcceptablePacketDelayVariance(int16_t latencyVarianceInMilliseconds) { + _maxAcceptablePacketDelayVariance = latencyVarianceInMilliseconds; + } + + /** + * @param lossRatio Maximum acceptable packet loss ratio (PLR). + */ + void setMaxAcceptablePacketLossRatio(float lossRatio) { + _maxAcceptablePacketLossRatio = lossRatio; + } + + /** + * @param errorRatio Maximum acceptable packet error ratio (PER). + */ + void setMaxAcceptablePacketErrorRatio(float errorRatio) { + _maxAcceptablePacketErrorRatio = errorRatio; + } + + /** + * @param errorRatio Maximum acceptable packet error ratio (PER). + */ + void setMinAcceptableAllocation(float minAlloc) { + _minAcceptableAllocation = minAlloc * 255; + } + + /** + * @return Whether the user has defined slaves for use on this bond + */ + inline bool userHasSpecifiedSlaves() { return _userHasSpecifiedSlaves; } + + /** + * @return Whether the user has defined a set of failover slave(s) for this bond + */ + inline bool userHasSpecifiedFailoverInstructions() { return _userHasSpecifiedFailoverInstructions; }; + + /** + * @return Whether the user has specified a primary slave + */ + inline bool userHasSpecifiedPrimarySlave() { return _userHasSpecifiedPrimarySlave; } + + /** + * @return Whether the user has specified slave speeds + */ + inline bool userHasSpecifiedSlaveSpeeds() { return _userHasSpecifiedSlaveSpeeds; } + + /** + * Periodically perform maintenance tasks for each active bond. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param now Current time + */ + void processBackgroundTasks(void *tPtr, int64_t now); + + /** + * Rate limit gate for VERB_ACK + * + * @param now Current time + * @return Whether the incoming packet should be rate-gated + */ + inline bool rateGateACK(const int64_t now) + { + _ackCutoffCount++; + int numToDrain = _lastAckRateCheck ? (now - _lastAckRateCheck) / ZT_ACK_DRAINAGE_DIVISOR : _ackCutoffCount; + _lastAckRateCheck = now; + if (_ackCutoffCount > numToDrain) { + _ackCutoffCount-=numToDrain; + } else { + _ackCutoffCount = 0; + } + return (_ackCutoffCount < ZT_ACK_CUTOFF_LIMIT); + } + + /** + * Rate limit gate for VERB_QOS_MEASUREMENT + * + * @param now Current time + * @return Whether the incoming packet should be rate-gated + */ + inline bool rateGateQoS(const int64_t now) + { + _qosCutoffCount++; + int numToDrain = (now - _lastQoSRateCheck) / ZT_QOS_DRAINAGE_DIVISOR; + _lastQoSRateCheck = now; + if (_qosCutoffCount > numToDrain) { + _qosCutoffCount-=numToDrain; + } else { + _qosCutoffCount = 0; + } + return (_qosCutoffCount < ZT_QOS_CUTOFF_LIMIT); + } + + /** + * Rate limit gate for VERB_PATH_NEGOTIATION_REQUEST + * + * @param now Current time + * @return Whether the incoming packet should be rate-gated + */ + inline bool rateGatePathNegotiation(const int64_t now) + { + if ((now - _lastPathNegotiationReceived) <= ZT_PATH_NEGOTIATION_CUTOFF_TIME) + ++_pathNegotiationCutoffCount; + else _pathNegotiationCutoffCount = 0; + _lastPathNegotiationReceived = now; + return (_pathNegotiationCutoffCount < ZT_PATH_NEGOTIATION_CUTOFF_LIMIT); + } + + /** + * @param interval Maximum amount of time user expects a failover to take on this bond. + */ + inline void setFailoverInterval(uint32_t interval) { _failoverInterval = interval; } + + /** + * @param strategy The strategy that the bond uses to prob for path aliveness and quality + */ + inline void setSlaveMonitorStrategy(uint8_t strategy) { _slaveMonitorStrategy = strategy; } + + /** + * @return the current up delay parameter + */ + inline uint16_t getUpDelay() { return _upDelay; } + + /** + * @param upDelay Length of time before a newly-discovered path is admitted to the bond + */ + inline void setUpDelay(int upDelay) { if (upDelay >= 0) { _upDelay = upDelay; } } + + /** + * @return Length of time before a newly-failed path is removed from the bond + */ + inline uint16_t getDownDelay() { return _downDelay; } + + /** + * @param downDelay Length of time before a newly-failed path is removed from the bond + */ + inline void setDownDelay(int downDelay) { if (downDelay >= 0) { _downDelay = downDelay; } } + + /** + * @return the current monitoring interval for the bond (can be overridden with intervals specific to certain slaves.) + */ + inline uint16_t getBondMonitorInterval() { return _bondMonitorInterval; } + + /** + * Set the current monitoring interval for the bond (can be overridden with intervals specific to certain slaves.) + * + * @param monitorInterval How often gratuitous VERB_HELLO(s) are sent to remote peer. + */ + inline void setBondMonitorInterval(uint16_t interval) { _bondMonitorInterval = interval; } + + /** + * @param policy Bonding policy for this bond + */ + inline void setPolicy(uint8_t policy) { _bondingPolicy = policy; } + + /** + * @return the current bonding policy + */ + inline uint8_t getPolicy() { return _bondingPolicy; } + + /** + * + * @param allowFlowHashing + */ + inline void setFlowHashing(bool allowFlowHashing) { _allowFlowHashing = allowFlowHashing; } + + /** + * @return Whether flow-hashing is currently enabled for this bond. + */ + bool flowHashingEnabled() { return _allowFlowHashing; } + + /** + * + * @param packetsPerSlave + */ + inline void setPacketsPerSlave(int packetsPerSlave) { _packetsPerSlave = packetsPerSlave; } + + /** + * + * @param slaveSelectMethod + */ + inline void setSlaveSelectMethod(uint8_t method) { _abSlaveSelectMethod = method; } + + /** + * + * @return + */ + inline uint8_t getSlaveSelectMethod() { return _abSlaveSelectMethod; } + + /** + * + * @param allowPathNegotiation + */ + inline void setAllowPathNegotiation(bool allowPathNegotiation) { _allowPathNegotiation = allowPathNegotiation; } + + /** + * + * @return + */ + inline bool allowPathNegotiation() { return _allowPathNegotiation; } + +private: + + const RuntimeEnvironment *RR; + AtomicCounter __refCount; + + /** + * Custom name given by the user to this bond type. + */ + std::string _policyAlias; + + /** + * Paths that this bond has been made aware of but that are not necessarily + * part of the bond proper. + */ + SharedPtr _paths[ZT_MAX_PEER_NETWORK_PATHS]; + + /** + * Set of indices corresponding to paths currently included in the bond proper. This + * may only be updated during a call to curateBond(). The reason for this is so that + * we can simplify the high frequency packet egress logic. + */ + int _bondedIdx[ZT_MAX_PEER_NETWORK_PATHS]; + + /** + * Number of paths currently included in the _bondedIdx set. + */ + int _numBondedPaths; + + /** + * Flows hashed according to port and protocol + */ + std::map > _flows; + + float _qualityWeights[ZT_QOS_WEIGHT_SIZE]; // How much each factor contributes to the "quality" score of a path. + + uint8_t _bondingPolicy; + uint32_t _upDelay; + uint32_t _downDelay; + + // active-backup + SharedPtr _abPath; // current active path + std::list > _abFailoverQueue; + uint8_t _abSlaveSelectMethod; // slave re-selection policy for the primary slave in active-backup + uint64_t _lastActiveBackupPathChange; + + // balance-rr + uint8_t _rrIdx; // index to path currently in use during Round Robin operation + uint16_t _rrPacketsSentOnCurrSlave; // number of packets sent on this slave since the most recent path switch. + /** + * How many packets will be sent on a path before moving to the next path + * in the round-robin sequence. A value of zero will cause a random path + * selection for each outgoing packet. + */ + int _packetsPerSlave; + + // balance-aware + uint64_t _totalBondUnderload; + + // dynamic slave monitoring + uint8_t _slaveMonitorStrategy; + uint64_t _lastFrame; + uint32_t _dynamicPathMonitorInterval; + + // path negotiation + int16_t _localUtility; + SharedPtr negotiatedPath; + uint8_t _numSentPathNegotiationRequests; + unsigned int _pathNegotiationCutoffCount; + bool _allowPathNegotiation; + uint64_t _lastPathNegotiationReceived; + uint64_t _lastSentPathNegotiationRequest; + + // timers + uint32_t _failoverInterval; + uint32_t _qosSendInterval; + uint32_t _ackSendInterval; + uint16_t _ackCutoffCount; + uint64_t _lastAckRateCheck; + uint16_t _qosCutoffCount; + uint64_t _lastQoSRateCheck; + uint32_t throughputMeasurementInterval; + uint32_t _qualityEstimationInterval; + + // timestamps + uint64_t _lastCheckUserPreferences; + uint64_t _lastQualityEstimation; + uint64_t _lastFlowStatReset; + uint64_t _lastFlowExpirationCheck; + uint64_t _lastFlowRebalance; + uint64_t _lastPathNegotiationCheck; + uint64_t _lastBackgroundTaskCheck; + + float _maxAcceptablePacketLossRatio; + float _maxAcceptablePacketErrorRatio; + uint16_t _maxAcceptableLatency; + uint16_t _maxAcceptableMeanLatency; + uint16_t _maxAcceptablePacketDelayVariance; + uint8_t _minAcceptableAllocation; + + /** + * Default initial punishment inflicted on misbehaving paths. Punishment slowly + * drains linearly. For each eligibility change the remaining punishment is doubled. + */ + uint32_t _defaultPathRefractoryPeriod; + + /** + * Whether the current bonding policy requires computation of path statistics + */ + bool _shouldCollectPathStatistics; + + /** + * Free byte of entropy that is updated on every packet egress event. + */ + unsigned char _freeRandomByte; + + /** + * Remote peer that this bond services + */ + SharedPtr _peer; + + Mutex _paths_m; + Mutex _flows_m; + + /** + * Whether the user has specified slaves for this bond. + */ + bool _userHasSpecifiedSlaves; + + /** + * Whether the user has specified a primary slave for this bond. + */ + bool _userHasSpecifiedPrimarySlave; + + /** + * Whether the user has specified failover instructions for this bond. + */ + bool _userHasSpecifiedFailoverInstructions; + + /** + * Whether the user has specified slaves speeds for this bond. + */ + bool _userHasSpecifiedSlaveSpeeds; + + /** + * How frequently (in ms) a VERB_ECHO is sent to a peer to verify that a + * path is still active. A value of zero (0) will disable active path + * monitoring; as result, all monitoring will be a function of traffic. + */ + uint16_t _bondMonitorInterval; + + /** + * Whether or not flow hashing is allowed. + */ + bool _allowFlowHashing; +}; + +} // namespace ZeroTier + +#endif \ No newline at end of file diff --git a/node/BondController.cpp b/node/BondController.cpp new file mode 100644 index 000000000..4bc8d2261 --- /dev/null +++ b/node/BondController.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2024-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +#include "BondController.hpp" +#include "Peer.hpp" + +namespace ZeroTier { + +int BondController::_minReqPathMonitorInterval; +uint8_t BondController::_defaultBondingPolicy; + +BondController::BondController(const RuntimeEnvironment *renv) : + RR(renv) +{ + bondStartTime = RR->node->now(); +} + +bool BondController::slaveAllowed(std::string &policyAlias, SharedPtr slave) +{ + bool foundInDefinitions = false; + if (_slaveDefinitions.count(policyAlias)) { + auto it = _slaveDefinitions[policyAlias].begin(); + while (it != _slaveDefinitions[policyAlias].end()) { + if (slave->ifname() == (*it)->ifname()) { + foundInDefinitions = true; + break; + } + ++it; + } + } + return _slaveDefinitions[policyAlias].empty() || foundInDefinitions; +} + +void BondController::addCustomSlave(std::string& policyAlias, SharedPtr slave) +{ + Mutex::Lock _l(_slaves_m); + _slaveDefinitions[policyAlias].push_back(slave); + auto search = _interfaceToSlaveMap[policyAlias].find(slave->ifname()); + if (search == _interfaceToSlaveMap[policyAlias].end()) { + slave->setAsUserSpecified(true); + _interfaceToSlaveMap[policyAlias].insert(std::pair>(slave->ifname(), slave)); + } else { + fprintf(stderr, "slave already exists=%s\n", slave->ifname().c_str()); + // Slave is already defined, overlay user settings + } +} + +bool BondController::addCustomPolicy(const SharedPtr& newBond) +{ + Mutex::Lock _l(_bonds_m); + if (!_bondPolicyTemplates.count(newBond->policyAlias())) { + _bondPolicyTemplates[newBond->policyAlias()] = newBond; + return true; + } + return false; +} + +bool BondController::assignBondingPolicyToPeer(int64_t identity, const std::string& policyAlias) +{ + Mutex::Lock _l(_bonds_m); + if (!_policyTemplateAssignments.count(identity)) { + _policyTemplateAssignments[identity] = policyAlias; + return true; + } + return false; +} + +SharedPtr BondController::createTransportTriggeredBond(const RuntimeEnvironment *renv, const SharedPtr& peer) +{ + fprintf(stderr, "createTransportTriggeredBond\n"); + Mutex::Lock _l(_bonds_m); + int64_t identity = peer->identity().address().toInt(); + Bond *bond = nullptr; + if (!_bonds.count(identity)) { + std::string policyAlias; + int _defaultBondingPolicy = defaultBondingPolicy(); + fprintf(stderr, "new bond, registering for %llx\n", identity); + if (!_policyTemplateAssignments.count(identity)) { + if (defaultBondingPolicy()) { + fprintf(stderr, " no assignment, using default (%d)\n", _defaultBondingPolicy); + bond = new Bond(renv, _defaultBondingPolicy, peer); + } + if (!_defaultBondingPolicy && _defaultBondingPolicyStr.length()) { + fprintf(stderr, " no assignment, using default custom (%s)\n", _defaultBondingPolicyStr.c_str()); + bond = new Bond(renv, *(_bondPolicyTemplates[_defaultBondingPolicyStr].ptr()), peer); + } + } + else { + fprintf(stderr, " assignment found for %llx, using it as a template (%s)\n", identity,_policyTemplateAssignments[identity].c_str()); + if (!_bondPolicyTemplates[_policyTemplateAssignments[identity]]) { + fprintf(stderr, "unable to locate template (%s), ignoring assignment for (%llx), using defaults\n", _policyTemplateAssignments[identity].c_str(), identity); + bond = new Bond(renv, _defaultBondingPolicy, peer); + } + else { + bond = new Bond(renv, *(_bondPolicyTemplates[_policyTemplateAssignments[identity]].ptr()), peer); + } + } + } + else { + fprintf(stderr, "bond already exists for %llx, cannot re-register. exiting\n", identity); exit(0); // TODO: Remove + } + if (bond) { + _bonds[identity] = bond; + /** + * Determine if user has specified anything that could affect the bonding policy's decisions + */ + if (_interfaceToSlaveMap.count(bond->policyAlias())) { + std::map >::iterator it = _interfaceToSlaveMap[bond->policyAlias()].begin(); + while (it != _interfaceToSlaveMap[bond->policyAlias()].end()) { + if (it->second->isUserSpecified()) { + bond->_userHasSpecifiedSlaves = true; + } + if (it->second->isUserSpecified() && it->second->primary()) { + bond->_userHasSpecifiedPrimarySlave = true; + } + if (it->second->isUserSpecified() && it->second->userHasSpecifiedFailoverInstructions()) { + bond->_userHasSpecifiedFailoverInstructions = true; + } + if (it->second->isUserSpecified() && (it->second->speed() > 0)) { + bond->_userHasSpecifiedSlaveSpeeds = true; + } + ++it; + } + } + return bond; + } + return SharedPtr(); +} + +SharedPtr BondController::getSlaveBySocket(const std::string& policyAlias, uint64_t localSocket) +{ + Mutex::Lock _l(_slaves_m); + char ifname[16]; + _phy->getIfName((PhySocket *) ((uintptr_t)localSocket), ifname, 16); + std::string ifnameStr(ifname); + auto search = _interfaceToSlaveMap[policyAlias].find(ifnameStr); + if (search == _interfaceToSlaveMap[policyAlias].end()) { + SharedPtr s = new Slave(ifnameStr, 0, 0, 0, 0, 0, true, ZT_MULTIPATH_SLAVE_MODE_SPARE, "", 0.0); + _interfaceToSlaveMap[policyAlias].insert(std::pair >(ifnameStr, s)); + return s; + } + else { + return search->second; + } +} + +SharedPtr BondController::getSlaveByName(const std::string& policyAlias, const std::string& ifname) +{ + Mutex::Lock _l(_slaves_m); + auto search = _interfaceToSlaveMap[policyAlias].find(ifname); + if (search != _interfaceToSlaveMap[policyAlias].end()) { + return search->second; + } + return SharedPtr(); +} + +bool BondController::allowedToBind(const std::string& ifname) +{ + return true; + /* + if (!_defaultBondingPolicy) { + return true; // no restrictions + } + Mutex::Lock _l(_slaves_m); + if (_interfaceToSlaveMap.empty()) { + return true; // no restrictions + } + std::map > >::iterator policyItr = _interfaceToSlaveMap.begin(); + while (policyItr != _interfaceToSlaveMap.end()) { + std::map >::iterator slaveItr = policyItr->second.begin(); + while (slaveItr != policyItr->second.end()) { + if (slaveItr->first == ifname) { + return true; + } + ++slaveItr; + } + ++policyItr; + } + return false; + */ +} + +void BondController::processBackgroundTasks(void *tPtr, const int64_t now) +{ + Mutex::Lock _l(_bonds_m); + std::map >::iterator bondItr = _bonds.begin(); + while (bondItr != _bonds.end()) { + bondItr->second->processBackgroundTasks(tPtr, now); + ++bondItr; + } +} + +} // namespace ZeroTier \ No newline at end of file diff --git a/node/BondController.hpp b/node/BondController.hpp new file mode 100644 index 000000000..c8fa660b0 --- /dev/null +++ b/node/BondController.hpp @@ -0,0 +1,231 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2024-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +#ifndef ZT_BONDCONTROLLER_HPP +#define ZT_BONDCONTROLLER_HPP + +#include +#include + +#include "SharedPtr.hpp" +#include "../osdep/Phy.hpp" +#include "../osdep/Slave.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; +class Bond; +class Peer; + +class BondController +{ + friend class Bond; + +public: + + BondController(const RuntimeEnvironment *renv); + + /** + * @return The minimum interval required to poll the active bonds to fulfill all active monitoring timing requirements. + */ + bool slaveAllowed(std::string &policyAlias, SharedPtr slave); + + /** + * @return The minimum interval required to poll the active bonds to fulfill all active monitoring timing requirements. + */ + int minReqPathMonitorInterval() { return _minReqPathMonitorInterval; } + + /** + * @return The minimum interval required to poll the active bonds to fulfill all active monitoring timing requirements. + */ + static void setMinReqPathMonitorInterval(int minReqPathMonitorInterval) { _minReqPathMonitorInterval = minReqPathMonitorInterval; } + + /** + * @return Whether the bonding layer is currently set up to be used. + */ + bool inUse() { return !_bondPolicyTemplates.empty() || _defaultBondingPolicy; } + + /** + * @param basePolicyName Bonding policy name (See ZeroTierOne.h) + * @return The bonding policy code for a given human-readable bonding policy name + */ + static int getPolicyCodeByStr(const std::string& basePolicyName) + { + if (basePolicyName == "active-backup") { return 1; } + if (basePolicyName == "broadcast") { return 2; } + if (basePolicyName == "balance-rr") { return 3; } + if (basePolicyName == "balance-xor") { return 4; } + if (basePolicyName == "balance-aware") { return 5; } + return 0; // "none" + } + + /** + * @param policy Bonding policy code (See ZeroTierOne.h) + * @return The human-readable name for the given bonding policy code + */ + static std::string getPolicyStrByCode(int policy) + { + if (policy == 1) { return "active-backup"; } + if (policy == 2) { return "broadcast"; } + if (policy == 3) { return "balance-rr"; } + if (policy == 4) { return "balance-xor"; } + if (policy == 5) { return "balance-aware"; } + return "none"; + } + + /** + * Sets the default bonding policy for new or undefined bonds. + * + * @param bp Bonding policy + */ + void setBondingLayerDefaultPolicy(uint8_t bp) { _defaultBondingPolicy = bp; } + + /** + * Sets the default (custom) bonding policy for new or undefined bonds. + * + * @param alias Human-readable string alias for bonding policy + */ + void setBondingLayerDefaultPolicyStr(std::string alias) { _defaultBondingPolicyStr = alias; } + + /** + * @return The default bonding policy + */ + static int defaultBondingPolicy() { return _defaultBondingPolicy; } + + /** + * Add a user-defined slave to a given bonding policy. + * + * @param policyAlias User-defined custom name for variant of bonding policy + * @param slave Pointer to new slave definition + */ + void addCustomSlave(std::string& policyAlias, SharedPtr slave); + + /** + * Add a user-defined bonding policy that is based on one of the standard types. + * + * @param newBond Pointer to custom Bond object + * @return Whether a uniquely-named custom policy was successfully added + */ + bool addCustomPolicy(const SharedPtr& newBond); + + /** + * Assigns a specific bonding policy + * + * @param identity + * @param policyAlias + * @return + */ + bool assignBondingPolicyToPeer(int64_t identity, const std::string& policyAlias); + + /** + * Add a new bond to the bond controller. + * + * @param renv Runtime environment + * @param peer Remote peer that this bond services + * @return A pointer to the newly created Bond + */ + SharedPtr createTransportTriggeredBond(const RuntimeEnvironment *renv, const SharedPtr& peer); + + /** + * Periodically perform maintenance tasks for the bonding layer. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param now Current time + */ + void processBackgroundTasks(void *tPtr, int64_t now); + + /** + * Gets a reference to a physical slave definition given a policy alias and a local socket. + * + * @param policyAlias Policy in use + * @param localSocket Local source socket + * @return Physical slave definition + */ + SharedPtr getSlaveBySocket(const std::string& policyAlias, uint64_t localSocket); + + /** + * Gets a reference to a physical slave definition given its human-readable system name. + * + * @param policyAlias Policy in use + * @param ifname Alphanumeric human-readable name + * @return Physical slave definition + */ + SharedPtr getSlaveByName(const std::string& policyAlias, const std::string& ifname); + + /** + * @param ifname Name of interface that we want to know if we can bind to + */ + bool allowedToBind(const std::string& ifname); + + uint64_t getBondStartTime() { return bondStartTime; } + +private: + + Phy *_phy; + const RuntimeEnvironment *RR; + + Mutex _bonds_m; + Mutex _slaves_m; + + /** + * The last time that the bond controller updated the set of bonds. + */ + uint64_t _lastBackgroundBondControlTaskCheck; + + /** + * The minimum monitoring interval among all paths in this bond. + */ + static int _minReqPathMonitorInterval; + + /** + * The default bonding policy used for new bonds unless otherwise specified. + */ + static uint8_t _defaultBondingPolicy; + + /** + * The default bonding policy used for new bonds unless otherwise specified. + */ + std::string _defaultBondingPolicyStr; + + /** + * All currently active bonds. + */ + std::map > _bonds; + + /** + * Map of peers to custom bonding policies + */ + std::map _policyTemplateAssignments; + + /** + * User-defined bonding policies (can be assigned to a peer) + */ + std::map > _bondPolicyTemplates; + + /** + * Set of slaves defined for a given bonding policy + */ + std::map > > _slaveDefinitions; + + /** + * Set of slave objects mapped to their physical interfaces + */ + std::map > > _interfaceToSlaveMap; + + // TODO: Remove + uint64_t bondStartTime; +}; + +} // namespace ZeroTier + +#endif \ No newline at end of file diff --git a/node/Constants.hpp b/node/Constants.hpp index 4b88798df..c27e02319 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -192,7 +192,7 @@ /** * Minimum delay between timer task checks to prevent thrashing */ -#define ZT_CORE_TIMER_TASK_GRANULARITY 500 +#define ZT_CORE_TIMER_TASK_GRANULARITY 60 /** * How often Topology::clean() and Network::clean() and similar are called, in ms @@ -253,203 +253,6 @@ */ #define ZT_LOCAL_CONF_FILE_CHECK_INTERVAL 10000 -/** - * How long before we consider a flow to be dead and remove it from the balancing - * policy's list. - */ -#define ZT_MULTIPATH_FLOW_EXPIRATION 60000 - -/** - * How frequently to check for changes to the system's network interfaces. When - * the service decides to use this constant it's because we want to react more - * quickly to new interfaces that pop up or go down. - */ -#define ZT_MULTIPATH_BINDER_REFRESH_PERIOD 5000 - -/** - * Packets are only used for QoS/ACK statistical sampling if their packet ID is divisible by - * this integer. This is to provide a mechanism for both peers to agree on which packets need - * special treatment without having to exchange information. Changing this value would be - * a breaking change and would necessitate a protocol version upgrade. Since each incoming and - * outgoing packet ID is checked against this value its evaluation is of the form: - * (id & (divisor - 1)) == 0, thus the divisor must be a power of 2. - * - * This value is set at (16) so that given a normally-distributed RNG output we will sample - * 1/16th (or ~6.25%) of packets. - */ -#define ZT_PATH_QOS_ACK_PROTOCOL_DIVISOR 0x10 - -/** - * Time horizon for VERB_QOS_MEASUREMENT and VERB_ACK packet processing cutoff - */ -#define ZT_PATH_QOS_ACK_CUTOFF_TIME 30000 - -/** - * Maximum number of VERB_QOS_MEASUREMENT and VERB_ACK packets allowed to be - * processed within cutoff time. Separate totals are kept for each type but - * the limit is the same for both. - * - * This limits how often this peer will compute statistical estimates - * of various QoS measures from a VERB_QOS_MEASUREMENT or VERB_ACK packets to - * CUTOFF_LIMIT times per CUTOFF_TIME milliseconds per peer to prevent - * this from being useful for DOS amplification attacks. - */ -#define ZT_PATH_QOS_ACK_CUTOFF_LIMIT 128 - -/** - * Path choice history window size. This is used to keep track of which paths were - * previously selected so that we can maintain a target allocation over time. - */ -#define ZT_MULTIPATH_PROPORTION_WIN_SZ 128 - -/** - * How often we will sample packet latency. Should be at least greater than ZT_PING_CHECK_INVERVAL - * since we will record a 0 bit/s measurement if no valid latency measurement was made within this - * window of time. - */ -#define ZT_PATH_LATENCY_SAMPLE_INTERVAL (ZT_MULTIPATH_PEER_PING_PERIOD * 2) - -/** - * Interval used for rate-limiting the computation of path quality estimates. - */ -#define ZT_PATH_QUALITY_COMPUTE_INTERVAL 1000 - -/** - * Number of samples to consider when computing real-time path statistics - */ -#define ZT_PATH_QUALITY_METRIC_REALTIME_CONSIDERATION_WIN_SZ 128 - -/** - * Number of samples to consider when computing performing long-term path quality analysis. - * By default this value is set to ZT_PATH_QUALITY_METRIC_REALTIME_CONSIDERATION_WIN_SZ but can - * be set to any value greater than that to observe longer-term path quality behavior. - */ -#define ZT_PATH_QUALITY_METRIC_WIN_SZ ZT_PATH_QUALITY_METRIC_REALTIME_CONSIDERATION_WIN_SZ - -/** - * Maximum acceptable Packet Delay Variance (PDV) over a path - */ -#define ZT_PATH_MAX_PDV 1000 - -/** - * Maximum acceptable time interval between expectation and receipt of at least one ACK over a path - */ -#define ZT_PATH_MAX_AGE 30000 - -/** - * Maximum acceptable mean latency over a path - */ -#define ZT_PATH_MAX_MEAN_LATENCY 1000 - -/** - * How much each factor contributes to the "stability" score of a path - */ - -#if 0 -#define ZT_PATH_CONTRIB_PDV (1.5 / 3.0) -#define ZT_PATH_CONTRIB_LATENCY (0.0 / 3.0) -#define ZT_PATH_CONTRIB_THROUGHPUT_DISTURBANCE (1.5 / 3.0) -#else -#define ZT_PATH_CONTRIB_PDV (1.0 / 3.0) -#define ZT_PATH_CONTRIB_LATENCY (1.0 / 3.0) -#define ZT_PATH_CONTRIB_THROUGHPUT_DISTURBANCE (1.0 / 3.0) -#endif - -/** - * How much each factor contributes to the "quality" score of a path - */ -#if 0 -#define ZT_PATH_CONTRIB_STABILITY (2.00 / 3.0) -#define ZT_PATH_CONTRIB_THROUGHPUT (0.50 / 3.0) -#define ZT_PATH_CONTRIB_SCOPE (0.50 / 3.0) -#else -#define ZT_PATH_CONTRIB_STABILITY (0.75 / 3.0) -#define ZT_PATH_CONTRIB_THROUGHPUT (1.50 / 3.0) -#define ZT_PATH_CONTRIB_SCOPE (0.75 / 3.0) -#endif - -/** - * How often a QoS packet is sent - */ -#define ZT_PATH_QOS_INTERVAL 3000 - -/** - * Min and max acceptable sizes for a VERB_QOS_MEASUREMENT packet - */ -#define ZT_PATH_MIN_QOS_PACKET_SZ 8 + 1 -#define ZT_PATH_MAX_QOS_PACKET_SZ 1400 - -/** - * How many ID:sojourn time pairs in a single QoS packet - */ -#define ZT_PATH_QOS_TABLE_SIZE ((ZT_PATH_MAX_QOS_PACKET_SZ * 8) / (64 + 16)) - -/** - * Maximum number of outgoing packets we monitor for QoS information - */ -#define ZT_PATH_MAX_OUTSTANDING_QOS_RECORDS 128 - -/** - * Timeout for QoS records - */ -#define ZT_PATH_QOS_TIMEOUT (ZT_PATH_QOS_INTERVAL * 2) - -/** - * How often the service tests the path throughput - */ -#define ZT_PATH_THROUGHPUT_MEASUREMENT_INTERVAL (ZT_PATH_ACK_INTERVAL * 8) - -/** - * Minimum amount of time between each ACK packet - */ -#define ZT_PATH_ACK_INTERVAL 1000 - -/** - * How often an aggregate link statistics report is emitted into this tracing system - */ -#define ZT_PATH_AGGREGATE_STATS_REPORT_INTERVAL 30000 - -/** - * How much an aggregate link's component paths can vary from their target allocation - * before the link is considered to be in a state of imbalance. - */ -#define ZT_PATH_IMBALANCE_THRESHOLD 0.20 - -/** - * Max allowable time spent in any queue - */ -#define ZT_QOS_TARGET 5 // ms - -/** - * Time period where the time spent in the queue by a packet should fall below - * target at least once - */ -#define ZT_QOS_INTERVAL 100 // ms - -/** - * The number of bytes that each queue is allowed to send during each DRR cycle. - * This approximates a single-byte-based fairness queuing scheme - */ -#define ZT_QOS_QUANTUM ZT_DEFAULT_MTU - -/** - * The maximum total number of packets that can be queued among all - * active/inactive, old/new queues - */ -#define ZT_QOS_MAX_ENQUEUED_PACKETS 1024 - -/** - * Number of QoS queues (buckets) - */ -#define ZT_QOS_NUM_BUCKETS 9 - -/** - * All unspecified traffic is put in this bucket. Anything in a bucket with a smaller - * value is de-prioritized. Anything in a bucket with a higher value is prioritized over - * other traffic. - */ -#define ZT_QOS_DEFAULT_BUCKET 0 - /** * How frequently to send heartbeats over in-use paths */ @@ -465,21 +268,6 @@ */ #define ZT_PEER_PING_PERIOD 60000 -/** - * Delay between full-fledge pings of directly connected peers. - * - * With multipath bonding enabled ping peers more often to measure - * packet loss and latency. This uses more bandwidth so is disabled - * by default to avoid increasing idle bandwidth use for regular - * links. - */ -#define ZT_MULTIPATH_PEER_PING_PERIOD (ZT_PEER_PING_PERIOD / 10) - -/** - * How long before we consider a path to be dead in rapid fail-over scenarios - */ -#define ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD 250 - /** * Paths are considered expired if they have not sent us a real packet in this long */ @@ -490,6 +278,210 @@ */ #define ZT_PEER_EXPIRED_PATH_TRIAL_PERIOD (ZT_PEER_PING_PERIOD * 10) +/** + * Outgoing packets are only used for QoS/ACK statistical sampling if their + * packet ID is divisible by this integer. This is to provide a mechanism for + * both peers to agree on which packets need special treatment without having + * to exchange information. Changing this value would be a breaking change and + * would necessitate a protocol version upgrade. Since each incoming and + * outgoing packet ID is checked against this value its evaluation is of the + * form: + * + * (id & (divisor - 1)) == 0, thus the divisor must be a power of 2. + * + * This value is set at (16) so that given a normally-distributed RNG output + * we will sample 1/16th (or ~6.25%) of packets. + */ +#define ZT_QOS_ACK_DIVISOR 0x2 + +/** + * Time horizon for VERB_QOS_MEASUREMENT and VERB_ACK packet processing cutoff + */ +#define ZT_QOS_ACK_CUTOFF_TIME 30000 + +/** + * Maximum number of VERB_QOS_MEASUREMENT and VERB_ACK packets allowed to be + * processed within cutoff time. Separate totals are kept for each type but + * the limit is the same for both. + * + * This limits how often this peer will compute statistical estimates + * of various QoS measures from a VERB_QOS_MEASUREMENT or VERB_ACK packets to + * CUTOFF_LIMIT times per CUTOFF_TIME milliseconds per peer to prevent + * this from being useful for DOS amplification attacks. + */ +#define ZT_QOS_ACK_CUTOFF_LIMIT 128 + +/** + * Minimum acceptable size for a VERB_QOS_MEASUREMENT packet + */ +#define ZT_QOS_MIN_PACKET_SIZE (8 + 1) + +/** + * Maximum acceptable size for a VERB_QOS_MEASUREMENT packet + */ +#define ZT_QOS_MAX_PACKET_SIZE 1400 + +/** + * How many ID:sojourn time pairs are in a single QoS packet + */ +#define ZT_QOS_TABLE_SIZE ((ZT_QOS_MAX_PACKET_SIZE * 8) / (64 + 16)) + +/** + * Maximum number of outgoing packets we monitor for QoS information + */ +#define ZT_QOS_MAX_OUTSTANDING_RECORDS (1024*16) + +/** + * Interval used for rate-limiting the computation of path quality estimates. + */ +#define ZT_QOS_COMPUTE_INTERVAL 1000 + +/** + * Number of samples to consider when processing real-time path statistics + */ +#define ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE 32 + +/** + * Number of samples to consider when processing long-term trends + */ +#define ZT_QOS_LONGTERM_SAMPLE_WIN_SIZE (ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE * 4) + +/** + * Max allowable time spent in any queue (in ms) + */ +#define ZT_AQM_TARGET 5 + +/** + * Time period where the time spent in the queue by a packet should fall below. + * target at least once. (in ms) + */ +#define ZT_AQM_INTERVAL 100 + +/** + * The number of bytes that each queue is allowed to send during each DRR cycle. + * This approximates a single-byte-based fairness queuing scheme. + */ +#define ZT_AQM_QUANTUM ZT_DEFAULT_MTU + +/** + * The maximum total number of packets that can be queued among all + * active/inactive, old/new queues. + */ +#define ZT_AQM_MAX_ENQUEUED_PACKETS 1024 + +/** + * Number of QoS queues (buckets) + */ +#define ZT_AQM_NUM_BUCKETS 9 + +/** + * All unspecified traffic is put in this bucket. Anything in a bucket with a + * smaller value is deprioritized. Anything in a bucket with a higher value is + prioritized over other traffic. + */ +#define ZT_AQM_DEFAULT_BUCKET 0 + +/** + * How long before we consider a path to be dead in the general sense. This is + * used while searching for default or alternative paths to try in the absence + * of direct guidance from the user or a selection policy. + */ +#define ZT_MULTIPATH_DEFAULT_FAILOVER_INTERVAL 10000 + +/** + * How often flows are evaluated + */ +#define ZT_MULTIPATH_FLOW_CHECK_INTERVAL 10000 + +/** + * How long before we consider a flow to be dead and remove it from the + * policy's list. + */ +#define ZT_MULTIPATH_FLOW_EXPIRATION_INTERVAL 30000 + +/** + * How often a flow's statistical counters are reset + */ +#define ZT_FLOW_STATS_RESET_INTERVAL ZT_MULTIPATH_FLOW_EXPIRATION_INTERVAL + +/** + * Maximum number of flows allowed before we start forcibly forgetting old ones + */ +#define ZT_FLOW_MAX_COUNT (1024*64) + +/** + * How often flows are rebalanced across slave interfaces (if at all) + */ +#define ZT_FLOW_MIN_REBALANCE_INTERVAL 5000 + +/** + * How often flows are rebalanced across slave interfaces (if at all) + */ +#define ZT_FLOW_REBALANCE_INTERVAL 5000 + +/** + * A defensive timer to prevent path quality metrics from being + * processed too often. + */ +#define ZT_BOND_BACKGROUND_TASK_MIN_INTERVAL ZT_CORE_TIMER_TASK_GRANULARITY + +/** + * How often a bonding policy's background tasks are processed, + * some need more frequent attention than others. + */ +#define ZT_MULTIPATH_ACTIVE_BACKUP_CHECK_INTERVAL ZT_CORE_TIMER_TASK_GRANULARITY + +/** + * Minimum amount of time (since a previous transition) before the active-backup bonding + * policy is allowed to transition to a different slave. Only valid for active-backup. + */ +#define ZT_MULTIPATH_MIN_ACTIVE_BACKUP_AUTOFLOP_INTERVAL 10000 + +/** + * How often a peer checks that incoming (and outgoing) traffic on a bonded link is + * appropriately paired. + */ +#define ZT_PATH_NEGOTIATION_CHECK_INTERVAL 15000 + +/** + * Time horizon for path negotiation paths cutoff + */ +#define ZT_PATH_NEGOTIATION_CUTOFF_TIME 60000 + +/** + * Maximum number of path negotiations within cutoff time + * + * This limits response to PATH_NEGOTIATION to CUTOFF_LIMIT responses + * per CUTOFF_TIME milliseconds per peer to prevent this from being + * useful for DOS amplification attacks. + */ +#define ZT_PATH_NEGOTIATION_CUTOFF_LIMIT 8 + +/** + * How many times a peer will attempt to petition another peer to synchronize its + * traffic to the same path before giving up and surrendering to the other peer's preference. + */ +#define ZT_PATH_NEGOTIATION_TRY_COUNT 3 + +/** + * How much greater the quality of a path should be before an + * optimization procedure triggers a switch. + */ +#define ZT_MULTIPATH_ACTIVE_BACKUP_OPTIMIZE_MIN_THRESHOLD 0.10 + +/** + * Artificially inflates the failover score for paths which meet + * certain non-performance-related policy ranking criteria. + */ +#define ZT_MULTIPATH_FAILOVER_HANDICAP_PREFERRED 500 +#define ZT_MULTIPATH_FAILOVER_HANDICAP_PRIMARY 1000 +#define ZT_MULTIPATH_FAILOVER_HANDICAP_NEGOTIATED 5000 + +/** + * An indicator that no flow is to be associated with the given packet + */ +#define ZT_QOS_NO_FLOW -1 + /** * Timeout for overall peer activity (measured from last receive) */ @@ -557,20 +549,32 @@ */ #define ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH 120000 -/** - * Interval between direct path pushes in milliseconds if we are currently in multipath - * mode. In this mode the distinction between ZT_DIRECT_PATH_PUSH_INTERVAL and - * ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH does not exist since we want to inform other - * peers of this peer's new link/address as soon as possible so that both peers can - * begin forming an aggregated link. - */ -#define ZT_DIRECT_PATH_PUSH_INTERVAL_MULTIPATH (ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH / 16) - /** * Time horizon for push direct paths cutoff */ #define ZT_PUSH_DIRECT_PATHS_CUTOFF_TIME 30000 +/** + * Drainage constants for VERB_ECHO rate-limiters + */ +#define ZT_ECHO_CUTOFF_LIMIT ((1000 / ZT_CORE_TIMER_TASK_GRANULARITY) * ZT_MAX_PEER_NETWORK_PATHS) +#define ZT_ECHO_DRAINAGE_DIVISOR (1000 / ZT_ECHO_CUTOFF_LIMIT) + +/** + * Drainage constants for VERB_QOS rate-limiters + */ +#define ZT_QOS_CUTOFF_LIMIT ((1000 / ZT_CORE_TIMER_TASK_GRANULARITY) * ZT_MAX_PEER_NETWORK_PATHS) +#define ZT_QOS_DRAINAGE_DIVISOR (1000 / ZT_QOS_CUTOFF_LIMIT) + +/** + * Drainage constants for VERB_ACK rate-limiters + */ +#define ZT_ACK_CUTOFF_LIMIT 128 +#define ZT_ACK_DRAINAGE_DIVISOR (1000 / ZT_ACK_CUTOFF_LIMIT) + +#define ZT_MULTIPATH_DEFAULT_REFRCTORY_PERIOD 8000 +#define ZT_MULTIPATH_MAX_REFRACTORY_PERIOD 600000 + /** * Maximum number of direct path pushes within cutoff time * diff --git a/node/Flow.hpp b/node/Flow.hpp new file mode 100644 index 000000000..cb8c3e4aa --- /dev/null +++ b/node/Flow.hpp @@ -0,0 +1,123 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2024-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +#ifndef ZT_FLOW_HPP +#define ZT_FLOW_HPP + +#include "Path.hpp" +#include "SharedPtr.hpp" + +namespace ZeroTier { + +/** + * A protocol flow that is identified by the origin and destination port. + */ +struct Flow +{ + /** + * @param flowId Given flow ID + * @param now Current time + */ + Flow(int32_t flowId, int64_t now) : + _flowId(flowId), + _bytesInPerUnitTime(0), + _bytesOutPerUnitTime(0), + _lastActivity(now), + _lastPathReassignment(0), + _assignedPath(SharedPtr()) + {} + + /** + * Reset flow statistics + */ + void resetByteCounts() + { + _bytesInPerUnitTime = 0; + _bytesOutPerUnitTime = 0; + } + + /** + * @return The Flow's ID + */ + int32_t id() { return _flowId; } + + /** + * @return Number of incoming bytes processed on this flow per unit time + */ + int64_t bytesInPerUnitTime() { return _bytesInPerUnitTime; } + + /** + * Record number of incoming bytes on this flow + * + * @param bytes Number of incoming bytes + */ + void recordIncomingBytes(uint64_t bytes) { _bytesInPerUnitTime += bytes; } + + /** + * @return Number of outgoing bytes processed on this flow per unit time + */ + int64_t bytesOutPerUnitTime() { return _bytesOutPerUnitTime; } + + /** + * Record number of outgoing bytes on this flow + * + * @param bytes + */ + void recordOutgoingBytes(uint64_t bytes) { _bytesOutPerUnitTime += bytes; } + + /** + * @return The total number of bytes processed on this flow + */ + uint64_t totalBytes() { return _bytesInPerUnitTime + _bytesOutPerUnitTime; } + + /** + * How long since a packet was sent or received in this flow + * + * @param now Current time + * @return The age of the flow in terms of last recorded activity + */ + int64_t age(int64_t now) { return now - _lastActivity; } + + /** + * Record that traffic was processed on this flow at the given time. + * + * @param now Current time + */ + void updateActivity(int64_t now) { _lastActivity = now; } + + /** + * @return Path assigned to this flow + */ + SharedPtr assignedPath() { return _assignedPath; } + + /** + * @param path Assigned path over which this flow should be handled + */ + void assignPath(const SharedPtr &path, int64_t now) { + _assignedPath = path; + _lastPathReassignment = now; + } + + AtomicCounter __refCount; + + int32_t _flowId; + uint64_t _bytesInPerUnitTime; + uint64_t _bytesOutPerUnitTime; + int64_t _lastActivity; + int64_t _lastPathReassignment; + SharedPtr _assignedPath; +}; + +} // namespace ZeroTier + +#endif \ No newline at end of file diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 331446ced..702c08090 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -35,10 +35,12 @@ #include "Tag.hpp" #include "Revocation.hpp" #include "Trace.hpp" +#include "Path.hpp" +#include "Bond.hpp" namespace ZeroTier { -bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr) +bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr,int32_t flowId) { const Address sourceAddress(source()); @@ -67,7 +69,7 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr) if (!trusted) { if (!dearmor(peer->key())) { RR->t->incomingPacketMessageAuthenticationFailure(tPtr,_path,packetId(),sourceAddress,hops(),"invalid MAC"); - _path->recordInvalidPacket(); + peer->recordIncomingInvalidPacket(_path); return true; } } @@ -78,11 +80,12 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr) } const Packet::Verb v = verb(); + bool r = true; switch(v) { //case Packet::VERB_NOP: default: // ignore unknown verbs, but if they pass auth check they are "received" - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),v,0,Packet::VERB_NOP,false,0); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),v,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); break; case Packet::VERB_HELLO: r = _doHELLO(RR,tPtr,true); break; case Packet::VERB_ACK: r = _doACK(RR,tPtr,peer); break; @@ -91,8 +94,8 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr) case Packet::VERB_OK: r = _doOK(RR,tPtr,peer); break; case Packet::VERB_WHOIS: r = _doWHOIS(RR,tPtr,peer); break; case Packet::VERB_RENDEZVOUS: r = _doRENDEZVOUS(RR,tPtr,peer); break; - case Packet::VERB_FRAME: r = _doFRAME(RR,tPtr,peer); break; - case Packet::VERB_EXT_FRAME: r = _doEXT_FRAME(RR,tPtr,peer); break; + case Packet::VERB_FRAME: r = _doFRAME(RR,tPtr,peer,flowId); break; + case Packet::VERB_EXT_FRAME: r = _doEXT_FRAME(RR,tPtr,peer,flowId); break; case Packet::VERB_ECHO: r = _doECHO(RR,tPtr,peer); break; case Packet::VERB_MULTICAST_LIKE: r = _doMULTICAST_LIKE(RR,tPtr,peer); break; case Packet::VERB_NETWORK_CREDENTIALS: r = _doNETWORK_CREDENTIALS(RR,tPtr,peer); break; @@ -103,6 +106,7 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr) case Packet::VERB_PUSH_DIRECT_PATHS: r = _doPUSH_DIRECT_PATHS(RR,tPtr,peer); break; case Packet::VERB_USER_MESSAGE: r = _doUSER_MESSAGE(RR,tPtr,peer); break; case Packet::VERB_REMOTE_TRACE: r = _doREMOTE_TRACE(RR,tPtr,peer); break; + case Packet::VERB_PATH_NEGOTIATION_REQUEST: r = _doPATH_NEGOTIATION_REQUEST(RR,tPtr,peer); break; } if (r) { RR->node->statsLogVerb((unsigned int)v,(unsigned int)size()); @@ -113,9 +117,6 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr) RR->sw->requestWhois(tPtr,RR->node->now(),sourceAddress); return false; } - } catch (int ztExcCode) { - RR->t->incomingPacketInvalid(tPtr,_path,packetId(),sourceAddress,hops(),verb(),"unexpected exception in tryDecode()"); - return true; } catch ( ... ) { RR->t->incomingPacketInvalid(tPtr,_path,packetId(),sourceAddress,hops(),verb(),"unexpected exception in tryDecode()"); return true; @@ -193,59 +194,59 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar default: break; } - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_ERROR,inRePacketId,inReVerb,false,networkId); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_ERROR,inRePacketId,inReVerb,false,networkId,ZT_QOS_NO_FLOW); return true; } bool IncomingPacket::_doACK(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - if (!peer->rateGateACK(RR->node->now())) + SharedPtr bond = peer->bond(); + if (!bond || !bond->rateGateACK(RR->node->now())) { return true; + } /* Dissect incoming ACK packet. From this we can estimate current throughput of the path, establish known * maximums and detect packet loss. */ - if (peer->localMultipathSupport()) { - int32_t ackedBytes; - if (payloadLength() != sizeof(ackedBytes)) { - return true; // ignore - } - memcpy(&ackedBytes, payload(), sizeof(ackedBytes)); - _path->receivedAck(RR->node->now(), Utils::ntoh(ackedBytes)); - peer->inferRemoteMultipathEnabled(); + int32_t ackedBytes; + if (payloadLength() != sizeof(ackedBytes)) { + return true; // ignore + } + memcpy(&ackedBytes, payload(), sizeof(ackedBytes)); + if (bond) { + bond->receivedAck(_path, RR->node->now(), Utils::ntoh(ackedBytes)); } - return true; } bool IncomingPacket::_doQOS_MEASUREMENT(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - if (!peer->rateGateQoS(RR->node->now())) + SharedPtr bond = peer->bond(); + if (!bond || !bond->rateGateQoS(RR->node->now())) { return true; + } /* Dissect incoming QoS packet. From this we can compute latency values and their variance. * The latency variance is used as a measure of "jitter". */ - if (peer->localMultipathSupport()) { - if (payloadLength() > ZT_PATH_MAX_QOS_PACKET_SZ || payloadLength() < ZT_PATH_MIN_QOS_PACKET_SZ) { - return true; // ignore - } - const int64_t now = RR->node->now(); - uint64_t rx_id[ZT_PATH_QOS_TABLE_SIZE]; - uint16_t rx_ts[ZT_PATH_QOS_TABLE_SIZE]; - char *begin = (char *)payload(); - char *ptr = begin; - int count = 0; - int len = payloadLength(); - // Read packet IDs and latency compensation intervals for each packet tracked by this QoS packet - while (ptr < (begin + len) && (count < ZT_PATH_QOS_TABLE_SIZE)) { - memcpy((void*)&rx_id[count], ptr, sizeof(uint64_t)); - ptr+=sizeof(uint64_t); - memcpy((void*)&rx_ts[count], ptr, sizeof(uint16_t)); - ptr+=sizeof(uint16_t); - count++; - } - _path->receivedQoS(now, count, rx_id, rx_ts); - peer->inferRemoteMultipathEnabled(); + if (payloadLength() > ZT_QOS_MAX_PACKET_SIZE || payloadLength() < ZT_QOS_MIN_PACKET_SIZE) { + return true; // ignore + } + const int64_t now = RR->node->now(); + uint64_t rx_id[ZT_QOS_TABLE_SIZE]; + uint16_t rx_ts[ZT_QOS_TABLE_SIZE]; + char *begin = (char *)payload(); + char *ptr = begin; + int count = 0; + unsigned int len = payloadLength(); + // Read packet IDs and latency compensation intervals for each packet tracked by this QoS packet + while (ptr < (begin + len) && (count < ZT_QOS_TABLE_SIZE)) { + memcpy((void*)&rx_id[count], ptr, sizeof(uint64_t)); + ptr+=sizeof(uint64_t); + memcpy((void*)&rx_ts[count], ptr, sizeof(uint16_t)); + ptr+=sizeof(uint16_t); + count++; + } + if (bond) { + bond->receivedQoS(_path, now, count, rx_id, rx_ts); } - return true; } @@ -441,11 +442,12 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool } outp.setAt(worldUpdateSizeAt,(uint16_t)(outp.size() - (worldUpdateSizeAt + 2))); + peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); outp.armor(peer->key(),true); _path->send(RR,tPtr,outp.data(),outp.size(),now); peer->setRemoteVersion(protoVersion,vMajor,vMinor,vRevision); // important for this to go first so received() knows the version - peer->received(tPtr,_path,hops(),pid,payloadLength(),Packet::VERB_HELLO,0,Packet::VERB_NOP,false,0); + peer->received(tPtr,_path,hops(),pid,payloadLength(),Packet::VERB_HELLO,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); return true; } @@ -493,7 +495,10 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP } if (!hops()) { - _path->updateLatency((unsigned int)latency,RR->node->now()); + SharedPtr bond = peer->bond(); + if (!bond) { + _path->updateLatency((unsigned int)latency,RR->node->now()); + } } peer->setRemoteVersion(vProto,vMajor,vMinor,vRevision); @@ -522,8 +527,7 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP if (network) { const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC,6),6),at(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_ADI)); const unsigned int count = at(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 4); - if (((ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 6) + (count * 5)) <= size()) - RR->mc->addMultiple(tPtr,RR->node->now(),networkId,mg,field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 6,count * 5),count,at(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS)); + RR->mc->addMultiple(tPtr,RR->node->now(),networkId,mg,field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 6,count * 5),count,at(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS)); } } break; @@ -556,7 +560,7 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP default: break; } - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_OK,inRePacketId,inReVerb,false,networkId); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_OK,inRePacketId,inReVerb,false,networkId,ZT_QOS_NO_FLOW); return true; } @@ -591,7 +595,7 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const Shar _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_WHOIS,0,Packet::VERB_NOP,false,0); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_WHOIS,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); return true; } @@ -615,13 +619,108 @@ bool IncomingPacket::_doRENDEZVOUS(const RuntimeEnvironment *RR,void *tPtr,const } } - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_RENDEZVOUS,0,Packet::VERB_NOP,false,0); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_RENDEZVOUS,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); return true; } -bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +// Returns true if packet appears valid; pos and proto will be set +static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsigned int &pos,unsigned int &proto) { + if (frameLen < 40) + return false; + pos = 40; + proto = frameData[6]; + while (pos <= frameLen) { + switch(proto) { + case 0: // hop-by-hop options + case 43: // routing + case 60: // destination options + case 135: // mobility options + if ((pos + 8) > frameLen) + return false; // invalid! + proto = frameData[pos]; + pos += ((unsigned int)frameData[pos + 1] * 8) + 8; + break; + + //case 44: // fragment -- we currently can't parse these and they are deprecated in IPv6 anyway + //case 50: + //case 51: // IPSec ESP and AH -- we have to stop here since this is encrypted stuff + default: + return true; + } + } + return false; // overflow == invalid +} + +bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,int32_t flowId) +{ + int32_t _flowId = ZT_QOS_NO_FLOW; + SharedPtr bond = peer->bond(); + if (bond && bond->flowHashingEnabled()) { + if (size() > ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD) { + const unsigned int etherType = at(ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE); + const unsigned int frameLen = size() - ZT_PROTO_VERB_FRAME_IDX_PAYLOAD; + const uint8_t *const frameData = reinterpret_cast(data()) + ZT_PROTO_VERB_FRAME_IDX_PAYLOAD; + + if (etherType == ZT_ETHERTYPE_IPV4 && (frameLen >= 20)) { + uint16_t srcPort = 0; + uint16_t dstPort = 0; + uint8_t proto = (reinterpret_cast(frameData)[9]); + const unsigned int headerLen = 4 * (reinterpret_cast(frameData)[0] & 0xf); + switch(proto) { + case 0x01: // ICMP + //flowId = 0x01; + break; + // All these start with 16-bit source and destination port in that order + case 0x06: // TCP + case 0x11: // UDP + case 0x84: // SCTP + case 0x88: // UDPLite + if (frameLen > (headerLen + 4)) { + unsigned int pos = headerLen + 0; + srcPort = (reinterpret_cast(frameData)[pos++]) << 8; + srcPort |= (reinterpret_cast(frameData)[pos]); + pos++; + dstPort = (reinterpret_cast(frameData)[pos++]) << 8; + dstPort |= (reinterpret_cast(frameData)[pos]); + _flowId = dstPort ^ srcPort ^ proto; + } + break; + } + } + + if (etherType == ZT_ETHERTYPE_IPV6 && (frameLen >= 40)) { + uint16_t srcPort = 0; + uint16_t dstPort = 0; + unsigned int pos; + unsigned int proto; + _ipv6GetPayload((const uint8_t *)frameData, frameLen, pos, proto); + switch(proto) { + case 0x3A: // ICMPv6 + //flowId = 0x3A; + break; + // All these start with 16-bit source and destination port in that order + case 0x06: // TCP + case 0x11: // UDP + case 0x84: // SCTP + case 0x88: // UDPLite + if (frameLen > (pos + 4)) { + srcPort = (reinterpret_cast(frameData)[pos++]) << 8; + srcPort |= (reinterpret_cast(frameData)[pos]); + pos++; + dstPort = (reinterpret_cast(frameData)[pos++]) << 8; + dstPort |= (reinterpret_cast(frameData)[pos]); + _flowId = dstPort ^ srcPort ^ proto; + } + break; + default: + break; + } + } + } + } + const uint64_t nwid = at(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID); const SharedPtr network(RR->node->network(nwid)); bool trustEstablished = false; @@ -641,13 +740,12 @@ bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,void *tPtr,const Shar return false; } } - - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_FRAME,0,Packet::VERB_NOP,trustEstablished,nwid); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_FRAME,0,Packet::VERB_NOP,trustEstablished,nwid,_flowId); return true; } -bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,int32_t flowId) { const uint64_t nwid = at(ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID); const SharedPtr network(RR->node->network(nwid)); @@ -676,7 +774,7 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const const uint8_t *const frameData = (const uint8_t *)field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD,frameLen); if ((!from)||(from == network->mac())) { - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true,nwid); // trustEstablished because COM is okay + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true,nwid,flowId); // trustEstablished because COM is okay return true; } @@ -687,19 +785,19 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const network->learnBridgeRoute(from,peer->address()); } else { RR->t->incomingNetworkFrameDropped(tPtr,network,_path,packetId(),size(),peer->address(),Packet::VERB_EXT_FRAME,from,to,"bridging not allowed (remote)"); - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true,nwid); // trustEstablished because COM is okay + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true,nwid,flowId); // trustEstablished because COM is okay return true; } } else if (to != network->mac()) { if (to.isMulticast()) { if (network->config().multicastLimit == 0) { RR->t->incomingNetworkFrameDropped(tPtr,network,_path,packetId(),size(),peer->address(),Packet::VERB_EXT_FRAME,from,to,"multicast disabled"); - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true,nwid); // trustEstablished because COM is okay + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true,nwid,flowId); // trustEstablished because COM is okay return true; } } else if (!network->config().permitsBridging(RR->identity.address())) { RR->t->incomingNetworkFrameDropped(tPtr,network,_path,packetId(),size(),peer->address(),Packet::VERB_EXT_FRAME,from,to,"bridging not allowed (local)"); - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true,nwid); // trustEstablished because COM is okay + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true,nwid,flowId); // trustEstablished because COM is okay return true; } } @@ -715,13 +813,15 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const outp.append((uint8_t)Packet::VERB_EXT_FRAME); outp.append((uint64_t)packetId()); outp.append((uint64_t)nwid); + const int64_t now = RR->node->now(); + peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); outp.armor(peer->key(),true); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true,nwid); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true,nwid,flowId); } else { - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,false,nwid); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,false,nwid,flowId); } return true; @@ -729,8 +829,10 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - if (!peer->rateGateEchoRequest(RR->node->now())) + uint64_t now = RR->node->now(); + if (!peer->rateGateEchoRequest(now)) { return true; + } const uint64_t pid = packetId(); Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); @@ -738,10 +840,11 @@ bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const Share outp.append((uint64_t)pid); if (size() > ZT_PACKET_IDX_PAYLOAD) outp.append(reinterpret_cast(data()) + ZT_PACKET_IDX_PAYLOAD,size() - ZT_PACKET_IDX_PAYLOAD); + peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); outp.armor(peer->key(),true); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); - peer->received(tPtr,_path,hops(),pid,payloadLength(),Packet::VERB_ECHO,0,Packet::VERB_NOP,false,0); + peer->received(tPtr,_path,hops(),pid,payloadLength(),Packet::VERB_ECHO,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); return true; } @@ -767,7 +870,7 @@ bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,void *tPtr,c RR->mc->add(tPtr,now,nwid,MulticastGroup(MAC(field(ptr + 8,6),6),at(ptr + 14)),peer->address()); } - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_LIKE,0,Packet::VERB_NOP,false,0); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_LIKE,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); return true; } @@ -889,7 +992,7 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *t } } - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_NETWORK_CREDENTIALS,0,Packet::VERB_NOP,trustEstablished,(network) ? network->id() : 0); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_NETWORK_CREDENTIALS,0,Packet::VERB_NOP,trustEstablished,(network) ? network->id() : 0,ZT_QOS_NO_FLOW); return true; } @@ -915,7 +1018,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,void _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } - peer->received(tPtr,_path,hopCount,requestPacketId,payloadLength(),Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP,false,nwid); + peer->received(tPtr,_path,hopCount,requestPacketId,payloadLength(),Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP,false,nwid,ZT_QOS_NO_FLOW); return true; } @@ -931,12 +1034,14 @@ bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,c outp.append((uint64_t)packetId()); outp.append((uint64_t)network->id()); outp.append((uint64_t)configUpdateId); + const int64_t now = RR->node->now(); + peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); outp.armor(peer->key(),true); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } } - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_NETWORK_CONFIG,0,Packet::VERB_NOP,false,(network) ? network->id() : 0); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_NETWORK_CONFIG,0,Packet::VERB_NOP,false,(network) ? network->id() : 0,ZT_QOS_NO_FLOW); return true; } @@ -979,12 +1084,13 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr outp.append((uint32_t)mg.adi()); const unsigned int gatheredLocally = RR->mc->gather(peer->address(),nwid,mg,outp,gatherLimit); if (gatheredLocally > 0) { + peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); outp.armor(peer->key(),true); _path->send(RR,tPtr,outp.data(),outp.size(),now); } } - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_GATHER,0,Packet::VERB_NOP,trustEstablished,nwid); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_GATHER,0,Packet::VERB_NOP,trustEstablished,nwid,ZT_QOS_NO_FLOW); return true; } @@ -1032,19 +1138,19 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, if (network->config().multicastLimit == 0) { RR->t->incomingNetworkFrameDropped(tPtr,network,_path,packetId(),size(),peer->address(),Packet::VERB_MULTICAST_FRAME,from,to.mac(),"multicast disabled"); - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,false,nwid); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,false,nwid,ZT_QOS_NO_FLOW); return true; } if ((frameLen > 0)&&(frameLen <= ZT_MAX_MTU)) { if (!to.mac().isMulticast()) { RR->t->incomingPacketInvalid(tPtr,_path,packetId(),source(),hops(),Packet::VERB_MULTICAST_FRAME,"destination not multicast"); - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,true,nwid); // trustEstablished because COM is okay + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,true,nwid,ZT_QOS_NO_FLOW); // trustEstablished because COM is okay return true; } if ((!from)||(from.isMulticast())||(from == network->mac())) { RR->t->incomingPacketInvalid(tPtr,_path,packetId(),source(),hops(),Packet::VERB_MULTICAST_FRAME,"invalid source MAC"); - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,true,nwid); // trustEstablished because COM is okay + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,true,nwid,ZT_QOS_NO_FLOW); // trustEstablished because COM is okay return true; } @@ -1058,7 +1164,7 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, network->learnBridgeRoute(from,peer->address()); } else { RR->t->incomingNetworkFrameDropped(tPtr,network,_path,packetId(),size(),peer->address(),Packet::VERB_MULTICAST_FRAME,from,to.mac(),"bridging not allowed (remote)"); - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,true,nwid); // trustEstablished because COM is okay + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,true,nwid,ZT_QOS_NO_FLOW); // trustEstablished because COM is okay return true; } } @@ -1076,12 +1182,14 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, outp.append((uint32_t)to.adi()); outp.append((unsigned char)0x02); // flag 0x02 = contains gather results if (RR->mc->gather(peer->address(),nwid,to,outp,gatherLimit)) { + const int64_t now = RR->node->now(); + peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); outp.armor(peer->key(),true); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } } - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,true,nwid); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,true,nwid,ZT_QOS_NO_FLOW); } else { _sendErrorNeedCredentials(RR,tPtr,peer,nwid); return false; @@ -1094,9 +1202,8 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,void *tPt { const int64_t now = RR->node->now(); - // First, subject this to a rate limit if (!peer->rateGatePushDirectPaths(now)) { - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_PUSH_DIRECT_PATHS,0,Packet::VERB_NOP,false,0); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_PUSH_DIRECT_PATHS,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); return true; } @@ -1108,8 +1215,6 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,void *tPt unsigned int ptr = ZT_PACKET_IDX_PAYLOAD + 2; while (count--) { // if ptr overflows Buffer will throw - // TODO: some flags are not yet implemented - unsigned int flags = (*this)[ptr++]; unsigned int extLen = at(ptr); ptr += 2; ptr += extLen; // unused right now @@ -1132,6 +1237,7 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,void *tPt } } break; case 6: { + const InetAddress a(field(ptr,16),16,at(ptr + 16)); if ( ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) && // not being told to forget @@ -1149,7 +1255,7 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,void *tPt ptr += addrLen; } - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_PUSH_DIRECT_PATHS,0,Packet::VERB_NOP,false,0); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_PUSH_DIRECT_PATHS,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); return true; } @@ -1165,7 +1271,7 @@ bool IncomingPacket::_doUSER_MESSAGE(const RuntimeEnvironment *RR,void *tPtr,con RR->node->postEvent(tPtr,ZT_EVENT_USER_MESSAGE,reinterpret_cast(&um)); } - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_USER_MESSAGE,0,Packet::VERB_NOP,false,0); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_USER_MESSAGE,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); return true; } @@ -1189,11 +1295,29 @@ bool IncomingPacket::_doREMOTE_TRACE(const RuntimeEnvironment *RR,void *tPtr,con } } - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_REMOTE_TRACE,0,Packet::VERB_NOP,false,0); + peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_REMOTE_TRACE,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); return true; } +bool IncomingPacket::_doPATH_NEGOTIATION_REQUEST(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + uint64_t now = RR->node->now(); + SharedPtr bond = peer->bond(); + if (!bond || !bond->rateGatePathNegotiation(now)) { + return true; + } + if (payloadLength() != sizeof(int16_t)) { + return true; + } + int16_t remoteUtility = 0; + memcpy(&remoteUtility, payload(), sizeof(int16_t)); + if (peer->bond()) { + peer->bond()->processIncomingPathNegotiationRequest(now, _path, Utils::ntoh(remoteUtility)); + } + return true; +} + void IncomingPacket::_sendErrorNeedCredentials(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,const uint64_t nwid) { Packet outp(source(),RR->identity.address(),Packet::VERB_ERROR); diff --git a/node/IncomingPacket.hpp b/node/IncomingPacket.hpp index cf9a6474f..b1032d99d 100644 --- a/node/IncomingPacket.hpp +++ b/node/IncomingPacket.hpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -100,7 +100,7 @@ public: * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call * @return True if decoding and processing is complete, false if caller should try again */ - bool tryDecode(const RuntimeEnvironment *RR,void *tPtr); + bool tryDecode(const RuntimeEnvironment *RR,void *tPtr,int32_t flowId); /** * @return Time of packet receipt / start of decode @@ -117,8 +117,8 @@ private: bool _doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); bool _doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); bool _doRENDEZVOUS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); - bool _doFRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); - bool _doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doFRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,int32_t flowId); + bool _doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,int32_t flowId); bool _doECHO(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); bool _doMULTICAST_LIKE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); bool _doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); @@ -129,6 +129,7 @@ private: bool _doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); bool _doUSER_MESSAGE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); bool _doREMOTE_TRACE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doPATH_NEGOTIATION_REQUEST(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); void _sendErrorNeedCredentials(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,const uint64_t nwid); diff --git a/node/Node.cpp b/node/Node.cpp index 5330b74c2..e71c1424c 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -48,6 +48,7 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,int64 _networks(8), _now(now), _lastPingCheck(0), + _lastGratuitousPingCheck(0), _lastHousekeepingRun(0), _lastMemoizedTraceSettings(0) { @@ -102,8 +103,9 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,int64 const unsigned long mcs = sizeof(Multicaster) + (((sizeof(Multicaster) & 0xf) != 0) ? (16 - (sizeof(Multicaster) & 0xf)) : 0); const unsigned long topologys = sizeof(Topology) + (((sizeof(Topology) & 0xf) != 0) ? (16 - (sizeof(Topology) & 0xf)) : 0); const unsigned long sas = sizeof(SelfAwareness) + (((sizeof(SelfAwareness) & 0xf) != 0) ? (16 - (sizeof(SelfAwareness) & 0xf)) : 0); + const unsigned long bc = sizeof(BondController) + (((sizeof(BondController) & 0xf) != 0) ? (16 - (sizeof(BondController) & 0xf)) : 0); - m = reinterpret_cast(::malloc(16 + ts + sws + mcs + topologys + sas)); + m = reinterpret_cast(::malloc(16 + ts + sws + mcs + topologys + sas + bc)); if (!m) throw std::bad_alloc(); RR->rtmem = m; @@ -118,12 +120,15 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,int64 RR->topology = new (m) Topology(RR,tptr); m += topologys; RR->sa = new (m) SelfAwareness(RR); + m += sas; + RR->bc = new (m) BondController(RR); } catch ( ... ) { if (RR->sa) RR->sa->~SelfAwareness(); if (RR->topology) RR->topology->~Topology(); if (RR->mc) RR->mc->~Multicaster(); if (RR->sw) RR->sw->~Switch(); if (RR->t) RR->t->~Trace(); + if (RR->bc) RR->bc->~BondController(); ::free(m); throw; } @@ -142,6 +147,7 @@ Node::~Node() if (RR->mc) RR->mc->~Multicaster(); if (RR->sw) RR->sw->~Switch(); if (RR->t) RR->t->~Trace(); + if (RR->bc) RR->bc->~BondController(); ::free(RR->rtmem); } @@ -246,9 +252,23 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64 _now = now; Mutex::Lock bl(_backgroundTasksLock); + + unsigned long bondCheckInterval = ZT_CORE_TIMER_TASK_GRANULARITY; + if (RR->bc->inUse()) { + // Gratuitously ping active peers so that QoS metrics have enough data to work with (if active path monitoring is enabled) + bondCheckInterval = std::min(std::max(RR->bc->minReqPathMonitorInterval(), ZT_CORE_TIMER_TASK_GRANULARITY), ZT_PING_CHECK_INVERVAL); + if ((now - _lastGratuitousPingCheck) >= bondCheckInterval) { + Hashtable< Address,std::vector > alwaysContact; + _PingPeersThatNeedPing pfunc(RR,tptr,alwaysContact,now); + RR->topology->eachPeer<_PingPeersThatNeedPing &>(pfunc); + _lastGratuitousPingCheck = now; + } + RR->bc->processBackgroundTasks(tptr, now); + } + unsigned long timeUntilNextPingCheck = ZT_PING_CHECK_INVERVAL; const int64_t timeSinceLastPingCheck = now - _lastPingCheck; - if (timeSinceLastPingCheck >= ZT_PING_CHECK_INVERVAL) { + if (timeSinceLastPingCheck >= timeUntilNextPingCheck) { try { _lastPingCheck = now; @@ -354,7 +374,7 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64 } try { - *nextBackgroundTaskDeadline = now + (int64_t)std::max(std::min(timeUntilNextPingCheck,RR->sw->doTimerTasks(tptr,now)),(unsigned long)ZT_CORE_TIMER_TASK_GRANULARITY); + *nextBackgroundTaskDeadline = now + (int64_t)std::max(std::min(bondCheckInterval,std::min(timeUntilNextPingCheck,RR->sw->doTimerTasks(tptr,now))),(unsigned long)ZT_CORE_TIMER_TASK_GRANULARITY); } catch ( ... ) { return ZT_RESULT_FATAL_ERROR_INTERNAL; } @@ -461,7 +481,7 @@ ZT_PeerList *Node::peers() const for(std::vector< std::pair< Address,SharedPtr > >::iterator pi(peers.begin());pi!=peers.end();++pi) { ZT_Peer *p = &(pl->peers[pl->peerCount++]); p->address = pi->second->address().toInt(); - p->hadAggregateLink = 0; + p->isBonded = 0; if (pi->second->remoteVersionKnown()) { p->versionMajor = pi->second->remoteVersionMajor(); p->versionMinor = pi->second->remoteVersionMinor(); @@ -478,28 +498,24 @@ ZT_PeerList *Node::peers() const std::vector< SharedPtr > paths(pi->second->paths(_now)); SharedPtr bestp(pi->second->getAppropriatePath(_now,false)); - p->hadAggregateLink |= pi->second->hasAggregateLink(); p->pathCount = 0; for(std::vector< SharedPtr >::iterator path(paths.begin());path!=paths.end();++path) { memcpy(&(p->paths[p->pathCount].address),&((*path)->address()),sizeof(struct sockaddr_storage)); + //memcpy(&(p->paths[p->pathCount].ifname,&((*path)->slave()),32);) + p->paths[p->pathCount].localSocket = (*path)->localSocket(); p->paths[p->pathCount].lastSend = (*path)->lastOut(); p->paths[p->pathCount].lastReceive = (*path)->lastIn(); p->paths[p->pathCount].trustedPathId = RR->topology->getOutboundPathTrust((*path)->address()); p->paths[p->pathCount].expired = 0; p->paths[p->pathCount].preferred = ((*path) == bestp) ? 1 : 0; - p->paths[p->pathCount].latency = (float)(*path)->latency(); - p->paths[p->pathCount].packetDelayVariance = (*path)->packetDelayVariance(); - p->paths[p->pathCount].throughputDisturbCoeff = (*path)->throughputDisturbanceCoefficient(); - p->paths[p->pathCount].packetErrorRatio = (*path)->packetErrorRatio(); - p->paths[p->pathCount].packetLossRatio = (*path)->packetLossRatio(); - p->paths[p->pathCount].stability = (*path)->lastComputedStability(); - p->paths[p->pathCount].throughput = (*path)->meanThroughput(); - p->paths[p->pathCount].maxThroughput = (*path)->maxLifetimeThroughput(); - p->paths[p->pathCount].allocation = (float)(*path)->allocation() / (float)255; - p->paths[p->pathCount].ifname = (*path)->getName(); - + //p->paths[p->pathCount].age = (*path)->age(_now); + p->paths[p->pathCount].scope = (*path)->ipScope(); ++p->pathCount; } + if (pi->second->bond()) { + p->isBonded = pi->second->bond(); + p->bondingPolicy = pi->second->bond()->getPolicy(); + } } return pl; diff --git a/node/Node.hpp b/node/Node.hpp index 21d49f515..6461e4cd6 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -34,6 +34,7 @@ #include "Salsa20.hpp" #include "NetworkController.hpp" #include "Hashtable.hpp" +#include "BondController.hpp" // Bit mask for "expecting reply" hash #define ZT_EXPECTING_REPLIES_BUCKET_MASK1 255 @@ -186,6 +187,8 @@ public: inline const Identity &identity() const { return _RR.identity; } + inline BondController *bondController() const { return _RR.bc; } + /** * Register that we are expecting a reply to a packet ID * @@ -247,9 +250,6 @@ public: inline const Address &remoteTraceTarget() const { return _remoteTraceTarget; } inline Trace::Level remoteTraceLevel() const { return _remoteTraceLevel; } - inline void setMultipathMode(uint8_t mode) { _multipathMode = mode; } - inline uint8_t getMultipathMode() { return _multipathMode; } - inline bool localControllerHasAuthorized(const int64_t now,const uint64_t nwid,const Address &addr) const { _localControllerAuthorizations_m.lock(); @@ -306,10 +306,9 @@ private: Address _remoteTraceTarget; enum Trace::Level _remoteTraceLevel; - uint8_t _multipathMode; - volatile int64_t _now; int64_t _lastPingCheck; + int64_t _lastGratuitousPingCheck; int64_t _lastHousekeepingRun; int64_t _lastMemoizedTraceSettings; volatile int64_t _prngState[2]; diff --git a/node/Packet.cpp b/node/Packet.cpp index 25006416a..381864a45 100644 --- a/node/Packet.cpp +++ b/node/Packet.cpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. diff --git a/node/Packet.hpp b/node/Packet.hpp index 53a1883ce..ca789db81 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -931,13 +931,13 @@ public: * * Upon receipt of this packet, the local peer will verify that the correct * number of bytes were received by the remote peer. If these values do - * not agree that could be an indicator of packet loss. + * not agree that could be an indication of packet loss. * * Additionally, the local peer knows the interval of time that has * elapsed since the last received ACK. With this information it can compute * a rough estimate of the current throughput. * - * This is sent at a maximum rate of once per every ZT_PATH_ACK_INTERVAL + * This is sent at a maximum rate of once per every ZT_QOS_ACK_INTERVAL */ VERB_ACK = 0x12, @@ -963,7 +963,8 @@ public: * measure of the amount of time between when a packet was received and the * egress time of its tracking QoS packet. * - * This is sent at a maximum rate of once per every ZT_PATH_QOS_INTERVAL + * This is sent at a maximum rate of once per every + * ZT_QOS_MEASUREMENT_INTERVAL */ VERB_QOS_MEASUREMENT = 0x13, @@ -996,7 +997,34 @@ public: * node on startup. This is helpful in identifying traces from different * members of a cluster. */ - VERB_REMOTE_TRACE = 0x15 + VERB_REMOTE_TRACE = 0x15, + + /** + * A request to a peer to use a specific path in a multi-path scenario: + * <[2] 16-bit unsigned integer that encodes a path choice utility> + * + * This is sent when a node operating in multipath mode observes that + * its inbound and outbound traffic aren't going over the same path. The + * node will compute its perceived utility for using its chosen outbound + * path and send this to a peer in an attempt to petition it to send + * its traffic over this same path. + * + * Scenarios: + * + * (1) Remote peer utility is GREATER than ours: + * - Remote peer will refuse the petition and continue using current path + * (2) Remote peer utility is LESS than than ours: + * - Remote peer will accept the petition and switch to our chosen path + * (3) Remote peer utility is EQUAL to our own: + * - To prevent confusion and flapping, both side will agree to use the + * numerical values of their identities to determine which path to use. + * The peer with the greatest identity will win. + * + * If a node petitions a peer repeatedly with no effect it will regard + * that as a refusal by the remote peer, in this case if the utility is + * negligible it will voluntarily switch to the remote peer's chosen path. + */ + VERB_PATH_NEGOTIATION_REQUEST = 0x16 }; /** diff --git a/node/Path.hpp b/node/Path.hpp index fc5dbff16..9c54f718f 100644 --- a/node/Path.hpp +++ b/node/Path.hpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -26,12 +26,11 @@ #include "SharedPtr.hpp" #include "AtomicCounter.hpp" #include "Utils.hpp" -#include "RingBuffer.hpp" #include "Packet.hpp" +#include "RingBuffer.hpp" +//#include "Bond.hpp" -#include "../osdep/Phy.hpp" - -#include "../include/ZeroTierDebug.h" +#include "../osdep/Slave.hpp" /** * Maximum return value of preferenceRank() @@ -48,7 +47,8 @@ class RuntimeEnvironment; class Path { friend class SharedPtr; - Phy *_phy; + friend class Bond; + //friend class SharedPtr; public: /** @@ -87,77 +87,113 @@ public: _lastOut(0), _lastIn(0), _lastTrustEstablishedPacketReceived(0), - _lastPathQualityComputeTime(0), _localSocket(-1), _latency(0xffff), _addr(), _ipScope(InetAddress::IP_SCOPE_NONE), - _lastAck(0), - _lastThroughputEstimation(0), + _lastAckReceived(0), + _lastAckSent(0), _lastQoSMeasurement(0), - _lastQoSRecordPurge(0), + _lastThroughputEstimation(0), + _lastRefractoryUpdate(0), + _lastAliveToggle(0), + _lastEligibilityState(false), + _lastTrialBegin(0), + _refractoryPeriod(0), + _monitorInterval(0), + _upDelay(0), + _downDelay(0), + _ipvPref(0), + _mode(0), + _onlyPathOnSlave(false), + _enabled(false), + _bonded(false), + _negotiated(false), + _deprecated(false), + _shouldReallocateFlows(false), + _assignedFlowCount(0), + _latencyMean(0), + _latencyVariance(0), + _packetLossRatio(0), + _packetErrorRatio(0), + _throughputMean(0), + _throughputMax(0), + _throughputVariance(0), + _allocation(0), + _byteLoad(0), + _relativeByteLoad(0), + _affinity(0), + _failoverScore(0), _unackedBytes(0), - _expectingAckAsOf(0), _packetsReceivedSinceLastAck(0), _packetsReceivedSinceLastQoS(0), - _maxLifetimeThroughput(0), - _lastComputedMeanThroughput(0), _bytesAckedSinceLastThroughputEstimation(0), - _lastComputedMeanLatency(0.0), - _lastComputedPacketDelayVariance(0.0), - _lastComputedPacketErrorRatio(0.0), - _lastComputedPacketLossRatio(0), - _lastComputedStability(0.0), - _lastComputedRelativeQuality(0), - _lastComputedThroughputDistCoeff(0.0), - _lastAllocation(0) - { - memset(_ifname, 0, 16); - memset(_addrString, 0, sizeof(_addrString)); - } + _packetsIn(0), + _packetsOut(0), + _prevEligibility(false) + {} Path(const int64_t localSocket,const InetAddress &addr) : _lastOut(0), _lastIn(0), _lastTrustEstablishedPacketReceived(0), - _lastPathQualityComputeTime(0), _localSocket(localSocket), _latency(0xffff), _addr(addr), _ipScope(addr.ipScope()), - _lastAck(0), - _lastThroughputEstimation(0), + _lastAckReceived(0), + _lastAckSent(0), _lastQoSMeasurement(0), - _lastQoSRecordPurge(0), + _lastThroughputEstimation(0), + _lastRefractoryUpdate(0), + _lastAliveToggle(0), + _lastEligibilityState(false), + _lastTrialBegin(0), + _refractoryPeriod(0), + _monitorInterval(0), + _upDelay(0), + _downDelay(0), + _ipvPref(0), + _mode(0), + _onlyPathOnSlave(false), + _enabled(false), + _bonded(false), + _negotiated(false), + _deprecated(false), + _shouldReallocateFlows(false), + _assignedFlowCount(0), + _latencyMean(0), + _latencyVariance(0), + _packetLossRatio(0), + _packetErrorRatio(0), + _throughputMean(0), + _throughputMax(0), + _throughputVariance(0), + _allocation(0), + _byteLoad(0), + _relativeByteLoad(0), + _affinity(0), + _failoverScore(0), _unackedBytes(0), - _expectingAckAsOf(0), _packetsReceivedSinceLastAck(0), _packetsReceivedSinceLastQoS(0), - _maxLifetimeThroughput(0), - _lastComputedMeanThroughput(0), _bytesAckedSinceLastThroughputEstimation(0), - _lastComputedMeanLatency(0.0), - _lastComputedPacketDelayVariance(0.0), - _lastComputedPacketErrorRatio(0.0), - _lastComputedPacketLossRatio(0), - _lastComputedStability(0.0), - _lastComputedRelativeQuality(0), - _lastComputedThroughputDistCoeff(0.0), - _lastAllocation(0) - { - memset(_ifname, 0, 16); - memset(_addrString, 0, sizeof(_addrString)); - if (_localSocket != -1) { - _phy->getIfName((PhySocket *) ((uintptr_t) _localSocket), _ifname, 16); - } - } + _packetsIn(0), + _packetsOut(0), + _prevEligibility(false) + {} /** * Called when a packet is received from this remote path, regardless of content * * @param t Time of receive */ - inline void received(const uint64_t t) { _lastIn = t; } + inline void received(const uint64_t t) { + _lastIn = t; + if (!_prevEligibility) { + _lastAliveToggle = _lastIn; + } + } /** * Set time last trusted packet was received (done in Peer::received()) @@ -197,7 +233,6 @@ public: else { _latency = l; } - _latencySamples.push(l); } /** @@ -286,341 +321,32 @@ public: } /** - * Record statistics on outgoing packets. Used later to estimate QoS metrics. - * - * @param now Current time - * @param packetId ID of packet - * @param payloadLength Length of payload - * @param verb Packet verb + * @param bonded Whether this path is part of a bond. */ - inline void recordOutgoingPacket(int64_t now, int64_t packetId, uint16_t payloadLength, Packet::Verb verb) - { - Mutex::Lock _l(_statistics_m); - if (verb != Packet::VERB_ACK && verb != Packet::VERB_QOS_MEASUREMENT) { - if ((packetId & (ZT_PATH_QOS_ACK_PROTOCOL_DIVISOR - 1)) == 0) { - _unackedBytes += payloadLength; - // Take note that we're expecting a VERB_ACK on this path as of a specific time - _expectingAckAsOf = ackAge(now) > ZT_PATH_ACK_INTERVAL ? _expectingAckAsOf : now; - if (_outQoSRecords.size() < ZT_PATH_MAX_OUTSTANDING_QOS_RECORDS) { - _outQoSRecords[packetId] = now; - } - } - } - } + inline void setBonded(bool bonded) { _bonded = bonded; } /** - * Record statistics on incoming packets. Used later to estimate QoS metrics. - * - * @param now Current time - * @param packetId ID of packet - * @param payloadLength Length of payload - * @param verb Packet verb + * @return True if this path is currently part of a bond. */ - inline void recordIncomingPacket(int64_t now, int64_t packetId, uint16_t payloadLength, Packet::Verb verb) - { - Mutex::Lock _l(_statistics_m); - if (verb != Packet::VERB_ACK && verb != Packet::VERB_QOS_MEASUREMENT) { - if ((packetId & (ZT_PATH_QOS_ACK_PROTOCOL_DIVISOR - 1)) == 0) { - _inACKRecords[packetId] = payloadLength; - _packetsReceivedSinceLastAck++; - _inQoSRecords[packetId] = now; - _packetsReceivedSinceLastQoS++; - } - _packetValiditySamples.push(true); - } - } - - /** - * Record that we've received a VERB_ACK on this path, also compute throughput if required. - * - * @param now Current time - * @param ackedBytes Number of bytes acknowledged by other peer - */ - inline void receivedAck(int64_t now, int32_t ackedBytes) - { - _expectingAckAsOf = 0; - _unackedBytes = (ackedBytes > _unackedBytes) ? 0 : _unackedBytes - ackedBytes; - int64_t timeSinceThroughputEstimate = (now - _lastThroughputEstimation); - if (timeSinceThroughputEstimate >= ZT_PATH_THROUGHPUT_MEASUREMENT_INTERVAL) { - uint64_t throughput = (uint64_t)((float)(_bytesAckedSinceLastThroughputEstimation * 8) / ((float)timeSinceThroughputEstimate / (float)1000)); - _throughputSamples.push(throughput); - _maxLifetimeThroughput = throughput > _maxLifetimeThroughput ? throughput : _maxLifetimeThroughput; - _lastThroughputEstimation = now; - _bytesAckedSinceLastThroughputEstimation = 0; - } else { - _bytesAckedSinceLastThroughputEstimation += ackedBytes; - } - } - - /** - * @return Number of bytes this peer is responsible for ACKing since last ACK - */ - inline int32_t bytesToAck() - { - Mutex::Lock _l(_statistics_m); - int32_t bytesToAck = 0; - std::map::iterator it = _inACKRecords.begin(); - while (it != _inACKRecords.end()) { - bytesToAck += it->second; - it++; - } - return bytesToAck; - } - - /** - * @return Number of bytes thus far sent that have not been acknowledged by the remote peer - */ - inline int64_t unackedSentBytes() - { - return _unackedBytes; - } - - /** - * Account for the fact that an ACK was just sent. Reset counters, timers, and clear statistics buffers - * - * @param Current time - */ - inline void sentAck(int64_t now) - { - Mutex::Lock _l(_statistics_m); - _inACKRecords.clear(); - _packetsReceivedSinceLastAck = 0; - _lastAck = now; - } - - /** - * Receive QoS data, match with recorded egress times from this peer, compute latency - * estimates. - * - * @param now Current time - * @param count Number of records - * @param rx_id table of packet IDs - * @param rx_ts table of holding times - */ - inline void receivedQoS(int64_t now, int count, uint64_t *rx_id, uint16_t *rx_ts) - { - Mutex::Lock _l(_statistics_m); - // Look up egress times and compute latency values for each record - std::map::iterator it; - for (int j=0; jsecond); - uint16_t rtt_compensated = rtt - rx_ts[j]; - uint16_t latency = rtt_compensated / 2; - updateLatency(latency, now); - _outQoSRecords.erase(it); - } - } - } - - /** - * Generate the contents of a VERB_QOS_MEASUREMENT packet. - * - * @param now Current time - * @param qosBuffer destination buffer - * @return Size of payload - */ - inline int32_t generateQoSPacket(int64_t now, char *qosBuffer) - { - Mutex::Lock _l(_statistics_m); - int32_t len = 0; - std::map::iterator it = _inQoSRecords.begin(); - int i=0; - while (i<_packetsReceivedSinceLastQoS && it != _inQoSRecords.end()) { - uint64_t id = it->first; - memcpy(qosBuffer, &id, sizeof(uint64_t)); - qosBuffer+=sizeof(uint64_t); - uint16_t holdingTime = (uint16_t)(now - it->second); - memcpy(qosBuffer, &holdingTime, sizeof(uint16_t)); - qosBuffer+=sizeof(uint16_t); - len+=sizeof(uint64_t)+sizeof(uint16_t); - _inQoSRecords.erase(it++); - i++; - } - return len; - } - - /** - * Account for the fact that a VERB_QOS_MEASUREMENT was just sent. Reset timers. - * - * @param Current time - */ - inline void sentQoS(int64_t now) { - _packetsReceivedSinceLastQoS = 0; - _lastQoSMeasurement = now; - } - - /** - * @param now Current time - * @return Whether an ACK (VERB_ACK) packet needs to be emitted at this time - */ - inline bool needsToSendAck(int64_t now) { - return ((now - _lastAck) >= ZT_PATH_ACK_INTERVAL || - (_packetsReceivedSinceLastAck == ZT_PATH_QOS_TABLE_SIZE)) && _packetsReceivedSinceLastAck; - } - - /** - * @param now Current time - * @return Whether a QoS (VERB_QOS_MEASUREMENT) packet needs to be emitted at this time - */ - inline bool needsToSendQoS(int64_t now) { - return ((_packetsReceivedSinceLastQoS >= ZT_PATH_QOS_TABLE_SIZE) || - ((now - _lastQoSMeasurement) > ZT_PATH_QOS_INTERVAL)) && _packetsReceivedSinceLastQoS; - } - - /** - * How much time has elapsed since we've been expecting a VERB_ACK on this path. This value - * is used to determine a more relevant path "age". This lets us penalize paths which are no - * longer ACKing, but not those that simple aren't being used to carry traffic at the - * current time. - */ - inline int64_t ackAge(int64_t now) { return _expectingAckAsOf ? now - _expectingAckAsOf : 0; } - - /** - * The maximum observed throughput (in bits/s) for this path - */ - inline uint64_t maxLifetimeThroughput() { return _maxLifetimeThroughput; } - - /** - * @return The mean throughput (in bits/s) of this link - */ - inline uint64_t meanThroughput() { return _lastComputedMeanThroughput; } - - /** - * Assign a new relative quality value for this path in the aggregate link - * - * @param rq Quality of this path in comparison to other paths available to this peer - */ - inline void updateRelativeQuality(float rq) { _lastComputedRelativeQuality = rq; } - - /** - * @return Quality of this path compared to others in the aggregate link - */ - inline float relativeQuality() { return _lastComputedRelativeQuality; } - - /** - * Assign a new allocation value for this path in the aggregate link - * - * @param allocation Percentage of traffic to be sent over this path to a peer - */ - inline void updateComponentAllocationOfAggregateLink(unsigned char allocation) { _lastAllocation = allocation; } - - /** - * @return Percentage of traffic allocated to this path in the aggregate link - */ - inline unsigned char allocation() { return _lastAllocation; } - - /** - * @return Stability estimates can become expensive to compute, we cache the most recent result. - */ - inline float lastComputedStability() { return _lastComputedStability; } - - /** - * @return A pointer to a cached copy of the human-readable name of the interface this Path's localSocket is bound to - */ - inline char *getName() { return _ifname; } - - /** - * @return Packet delay variance - */ - inline float packetDelayVariance() { return _lastComputedPacketDelayVariance; } - - /** - * @return Previously-computed mean latency - */ - inline float meanLatency() { return _lastComputedMeanLatency; } - - /** - * @return Packet loss rate (PLR) - */ - inline float packetLossRatio() { return _lastComputedPacketLossRatio; } - - /** - * @return Packet error ratio (PER) - */ - inline float packetErrorRatio() { return _lastComputedPacketErrorRatio; } - - /** - * Record an invalid incoming packet. This packet failed MAC/compression/cipher checks and will now - * contribute to a Packet Error Ratio (PER). - */ - inline void recordInvalidPacket() { _packetValiditySamples.push(false); } - - /** - * @return A pointer to a cached copy of the address string for this Path (For debugging only) - */ - inline char *getAddressString() { return _addrString; } - - /** - * @return The current throughput disturbance coefficient - */ - inline float throughputDisturbanceCoefficient() { return _lastComputedThroughputDistCoeff; } - - /** - * Compute and cache stability and performance metrics. The resultant stability coefficient is a measure of how "well behaved" - * this path is. This figure is substantially different from (but required for the estimation of the path's overall "quality". - * - * @param now Current time - */ - inline void processBackgroundPathMeasurements(const int64_t now) - { - if (now - _lastPathQualityComputeTime > ZT_PATH_QUALITY_COMPUTE_INTERVAL) { - Mutex::Lock _l(_statistics_m); - _lastPathQualityComputeTime = now; - address().toString(_addrString); - _lastComputedMeanLatency = _latencySamples.mean(); - _lastComputedPacketDelayVariance = _latencySamples.stddev(); // Similar to "jitter" (SEE: RFC 3393, RFC 4689) - _lastComputedMeanThroughput = (uint64_t)_throughputSamples.mean(); - - // If no packet validity samples, assume PER==0 - _lastComputedPacketErrorRatio = 1 - (_packetValiditySamples.count() ? _packetValiditySamples.mean() : 1); - - // Compute path stability - // Normalize measurements with wildly different ranges into a reasonable range - float normalized_pdv = Utils::normalize(_lastComputedPacketDelayVariance, 0, ZT_PATH_MAX_PDV, 0, 10); - float normalized_la = Utils::normalize(_lastComputedMeanLatency, 0, ZT_PATH_MAX_MEAN_LATENCY, 0, 10); - float throughput_cv = _throughputSamples.mean() > 0 ? _throughputSamples.stddev() / _throughputSamples.mean() : 1; - - // Form an exponential cutoff and apply contribution weights - float pdv_contrib = expf((-1.0f)*normalized_pdv) * (float)ZT_PATH_CONTRIB_PDV; - float latency_contrib = expf((-1.0f)*normalized_la) * (float)ZT_PATH_CONTRIB_LATENCY; - - // Throughput Disturbance Coefficient - float throughput_disturbance_contrib = expf((-1.0f)*throughput_cv) * (float)ZT_PATH_CONTRIB_THROUGHPUT_DISTURBANCE; - _throughputDisturbanceSamples.push(throughput_cv); - _lastComputedThroughputDistCoeff = _throughputDisturbanceSamples.mean(); - - // Obey user-defined ignored contributions - pdv_contrib = ZT_PATH_CONTRIB_PDV > 0.0 ? pdv_contrib : 1; - latency_contrib = ZT_PATH_CONTRIB_LATENCY > 0.0 ? latency_contrib : 1; - throughput_disturbance_contrib = ZT_PATH_CONTRIB_THROUGHPUT_DISTURBANCE > 0.0 ? throughput_disturbance_contrib : 1; - - // Stability - _lastComputedStability = pdv_contrib + latency_contrib + throughput_disturbance_contrib; - _lastComputedStability *= 1 - _lastComputedPacketErrorRatio; - - // Prevent QoS records from sticking around for too long - std::map::iterator it = _outQoSRecords.begin(); - while (it != _outQoSRecords.end()) { - // Time since egress of tracked packet - if ((now - it->second) >= ZT_PATH_QOS_TIMEOUT) { - _outQoSRecords.erase(it++); - } else { it++; } - } - } - } + inline bool bonded() { return _bonded; } /** * @return True if this path is alive (receiving heartbeats) */ - inline bool alive(const int64_t now) const { return ((now - _lastIn) < (ZT_PATH_HEARTBEAT_PERIOD + 5000)); } + inline bool alive(const int64_t now, bool bondingEnabled = false) const { + return (bondingEnabled && _monitorInterval) ? ((now - _lastIn) < (_monitorInterval * 3)) : ((now - _lastIn) < (ZT_PATH_HEARTBEAT_PERIOD + 5000)); + } /** * @return True if this path needs a heartbeat */ inline bool needsHeartbeat(const int64_t now) const { return ((now - _lastOut) >= ZT_PATH_HEARTBEAT_PERIOD); } + /** + * @return True if this path needs a heartbeat in accordance to the user-specified path monitor frequency + */ + inline bool needsGratuitousHeartbeat(const int64_t now) { return allowed() && (_monitorInterval > 0) && ((now - _lastOut) >= _monitorInterval); } + /** * @return Last time we sent something */ @@ -631,62 +357,339 @@ public: */ inline int64_t lastIn() const { return _lastIn; } + /** + * @return the age of the path in terms of receiving packets + */ + inline int64_t age(int64_t now) { return (now - _lastIn); } + /** * @return Time last trust-established packet was received */ inline int64_t lastTrustEstablishedPacketReceived() const { return _lastTrustEstablishedPacketReceived; } + /** + * @return Time since last VERB_ACK was received + */ + inline int64_t ackAge(int64_t now) { return _lastAckReceived ? now - _lastAckReceived : 0; } + + /** + * Set or update a refractory period for the path. + * + * @param punishment How much a path should be punished + * @param pathFailure Whether this call is the result of a recent path failure + */ + inline void adjustRefractoryPeriod(int64_t now, uint32_t punishment, bool pathFailure) { + if (pathFailure) { + unsigned int suggestedRefractoryPeriod = _refractoryPeriod ? punishment + (_refractoryPeriod * 2) : punishment; + _refractoryPeriod = std::min(suggestedRefractoryPeriod, (unsigned int)ZT_MULTIPATH_MAX_REFRACTORY_PERIOD); + _lastRefractoryUpdate = 0; + } else { + uint32_t drainRefractory = 0; + if (_lastRefractoryUpdate) { + drainRefractory = (now - _lastRefractoryUpdate); + } else { + drainRefractory = (now - _lastAliveToggle); + } + _lastRefractoryUpdate = now; + if (_refractoryPeriod > drainRefractory) { + _refractoryPeriod -= drainRefractory; + } else { + _refractoryPeriod = 0; + _lastRefractoryUpdate = 0; + } + } + } + + /** + * Determine the current state of eligibility of the path. + * + * @param includeRefractoryPeriod Whether current punishment should be taken into consideration + * @return True if this path can be used in a bond at the current time + */ + inline bool eligible(uint64_t now, int ackSendInterval, bool includeRefractoryPeriod = false) { + if (includeRefractoryPeriod && _refractoryPeriod) { + return false; + } + bool acceptableAge = age(now) < ((_monitorInterval * 4) + _downDelay); // Simple RX age (driven by packets of any type and gratuitous VERB_HELLOs) + bool acceptableAckAge = ackAge(now) < (ackSendInterval); // Whether the remote peer is actually responding to our outgoing traffic or simply sending stuff to us + bool notTooEarly = (now - _lastAliveToggle) >= _upDelay; // Whether we've waited long enough since the link last came online + bool inTrial = (now - _lastTrialBegin) < _upDelay; // Whether this path is still in its trial period + bool currEligibility = allowed() && (((acceptableAge || acceptableAckAge) && notTooEarly) || inTrial); + return currEligibility; + } + + /** + * Record when this path first entered the bond. Each path is given a trial period where it is admitted + * to the bond without requiring observations to prove its performance or reliability. + */ + inline void startTrial(uint64_t now) { _lastTrialBegin = now; } + + /** + * @return True if a path is permitted to be used in a bond (according to user pref.) + */ + inline bool allowed() { + return _enabled + && (!_ipvPref + || ((_addr.isV4() && (_ipvPref == 4 || _ipvPref == 46 || _ipvPref == 64)) + || ((_addr.isV6() && (_ipvPref == 6 || _ipvPref == 46 || _ipvPref == 64))))); + } + + /** + * @return True if a path is preferred over another on the same physical slave (according to user pref.) + */ + inline bool preferred() { + return _onlyPathOnSlave + || (_addr.isV4() && (_ipvPref == 4 || _ipvPref == 46)) + || (_addr.isV6() && (_ipvPref == 6 || _ipvPref == 64)); + } + + /** + * @param now Current time + * @return Whether an ACK (VERB_ACK) packet needs to be emitted at this time + */ + inline bool needsToSendAck(int64_t now, int ackSendInterval) { + return ((now - _lastAckSent) >= ackSendInterval || + (_packetsReceivedSinceLastAck == ZT_QOS_TABLE_SIZE)) && _packetsReceivedSinceLastAck; + } + + /** + * @param now Current time + * @return Whether a QoS (VERB_QOS_MEASUREMENT) packet needs to be emitted at this time + */ + inline bool needsToSendQoS(int64_t now, int qosSendInterval) { + return ((_packetsReceivedSinceLastQoS >= ZT_QOS_TABLE_SIZE) || + ((now - _lastQoSMeasurement) > qosSendInterval)) && _packetsReceivedSinceLastQoS; + } + + /** + * Reset packet counters + */ + inline void resetPacketCounts() + { + _packetsIn = 0; + _packetsOut = 0; + } + private: - Mutex _statistics_m; volatile int64_t _lastOut; volatile int64_t _lastIn; volatile int64_t _lastTrustEstablishedPacketReceived; - volatile int64_t _lastPathQualityComputeTime; int64_t _localSocket; volatile unsigned int _latency; InetAddress _addr; InetAddress::IpScope _ipScope; // memoize this since it's a computed value checked often AtomicCounter __refCount; - std::map _outQoSRecords; // id:egress_time - std::map _inQoSRecords; // id:now - std::map _inACKRecords; // id:len + std::map qosStatsOut; // id:egress_time + std::map qosStatsIn; // id:now + std::map ackStatsIn; // id:len - int64_t _lastAck; - int64_t _lastThroughputEstimation; - int64_t _lastQoSMeasurement; - int64_t _lastQoSRecordPurge; + RingBuffer qosRecordSize; + RingBuffer qosRecordLossSamples; + RingBuffer throughputSamples; + RingBuffer packetValiditySamples; + RingBuffer _throughputVarianceSamples; + RingBuffer latencySamples; + /** + * Last time that a VERB_ACK was received on this path. + */ + uint64_t _lastAckReceived; + + /** + * Last time that a VERB_ACK was sent out on this path. + */ + uint64_t _lastAckSent; + + /** + * Last time that a VERB_QOS_MEASUREMENT was sent out on this path. + */ + uint64_t _lastQoSMeasurement; + + /** + * Last time that a the path's throughput was estimated. + */ + uint64_t _lastThroughputEstimation; + + /** + * The last time that the refractory period was updated. + */ + uint64_t _lastRefractoryUpdate; + + /** + * The last time that the path was marked as "alive". + */ + uint64_t _lastAliveToggle; + + /** + * State of eligibility at last check. Used for determining state changes. + */ + bool _lastEligibilityState; + + /** + * Timestamp indicating when this path's trial period began. + */ + uint64_t _lastTrialBegin; + + /** + * Amount of time that this path is prevented from becoming a member of a bond. + */ + uint32_t _refractoryPeriod; + + /** + * Monitor interval specific to this path or that was inherited from the bond controller. + */ + int32_t _monitorInterval; + + /** + * Up delay interval specific to this path or that was inherited from the bond controller. + */ + uint32_t _upDelay; + + /** + * Down delay interval specific to this path or that was inherited from the bond controller. + */ + uint32_t _downDelay; + + /** + * IP version preference inherited from the physical slave. + */ + uint8_t _ipvPref; + + /** + * Mode inherited from the physical slave. + */ + uint8_t _mode; + + /** + * IP version preference inherited from the physical slave. + */ + bool _onlyPathOnSlave; + + /** + * Enabled state inherited from the physical slave. + */ + bool _enabled; + + /** + * Whether this path is currently part of a bond. + */ + bool _bonded; + + /** + * Whether this path was intentionally _negotiated by either peer. + */ + bool _negotiated; + + /** + * Whether this path has been deprecated due to performance issues. Current traffic flows + * will be re-allocated to other paths in the most non-disruptive manner (if possible), + * and new traffic will not be allocated to this path. + */ + bool _deprecated; + + /** + * Whether flows should be moved from this path. Current traffic flows will be re-allocated + * immediately. + */ + bool _shouldReallocateFlows; + + /** + * The number of flows currently assigned to this path. + */ + uint16_t _assignedFlowCount; + + /** + * The mean latency (computed from a sliding window.) + */ + float _latencyMean; + + /** + * Packet delay variance (computed from a sliding window.) + */ + float _latencyVariance; + + /** + * The ratio of lost packets to received packets. + */ + float _packetLossRatio; + + /** + * The ratio of packets that failed their MAC/CRC checks to those that did not. + */ + float _packetErrorRatio; + + /** + * The estimated mean throughput of this path. + */ + uint64_t _throughputMean; + + /** + * The maximum observed throughput of this path. + */ + uint64_t _throughputMax; + + /** + * The variance in the estimated throughput of this path. + */ + float _throughputVariance; + + /** + * The relative quality of this path to all others in the bond, [0-255]. + */ + uint8_t _allocation; + + /** + * How much load this path is under. + */ + uint64_t _byteLoad; + + /** + * How much load this path is under (relative to other paths in the bond.) + */ + uint8_t _relativeByteLoad; + + /** + * Relative value expressing how "deserving" this path is of new traffic. + */ + uint8_t _affinity; + + /** + * Score that indicates to what degree this path is preferred over others that + * are available to the bonding policy. (specifically for active-backup) + */ + uint32_t _failoverScore; + + /** + * Number of bytes thus far sent that have not been acknowledged by the remote peer. + */ int64_t _unackedBytes; - int64_t _expectingAckAsOf; - int16_t _packetsReceivedSinceLastAck; - int16_t _packetsReceivedSinceLastQoS; - uint64_t _maxLifetimeThroughput; - uint64_t _lastComputedMeanThroughput; + /** + * Number of packets received since the last VERB_ACK was sent to the remote peer. + */ + int32_t _packetsReceivedSinceLastAck; + + /** + * Number of packets received since the last VERB_QOS_MEASUREMENT was sent to the remote peer. + */ + int32_t _packetsReceivedSinceLastQoS; + + /** + * Bytes acknowledged via incoming VERB_ACK since the last estimation of throughput. + */ uint64_t _bytesAckedSinceLastThroughputEstimation; - float _lastComputedMeanLatency; - float _lastComputedPacketDelayVariance; + /** + * Counters used for tracking path load. + */ + int _packetsIn; + int _packetsOut; - float _lastComputedPacketErrorRatio; - float _lastComputedPacketLossRatio; + // TODO: Remove - // cached estimates - float _lastComputedStability; - float _lastComputedRelativeQuality; - float _lastComputedThroughputDistCoeff; - unsigned char _lastAllocation; - - // cached human-readable strings for tracing purposes - char _ifname[16]; - char _addrString[256]; - - RingBuffer _throughputSamples; - RingBuffer _latencySamples; - RingBuffer _packetValiditySamples; - RingBuffer _throughputDisturbanceSamples; + bool _prevEligibility; }; } // namespace ZeroTier diff --git a/node/Peer.cpp b/node/Peer.cpp index 3c45d53fb..1ee0c1240 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -14,7 +14,6 @@ #include "../version.h" #include "Constants.hpp" #include "Peer.hpp" -#include "Node.hpp" #include "Switch.hpp" #include "Network.hpp" #include "SelfAwareness.hpp" @@ -24,8 +23,6 @@ #include "RingBuffer.hpp" #include "Utils.hpp" -#include "../include/ZeroTierDebug.h" - namespace ZeroTier { static unsigned char s_freeRandomByteCounter = 0; @@ -37,20 +34,14 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident _lastTriedMemorizedPath(0), _lastDirectPathPushSent(0), _lastDirectPathPushReceive(0), + _lastEchoRequestReceived(0), _lastCredentialRequestSent(0), _lastWhoisRequestReceived(0), - _lastEchoRequestReceived(0), _lastCredentialsReceived(0), _lastTrustEstablishedPacketReceived(0), _lastSentFullHello(0), - _lastACKWindowReset(0), - _lastQoSWindowReset(0), - _lastMultipathCompatibilityCheck(0), + _lastEchoCheck(0), _freeRandomByte((unsigned char)((uintptr_t)this >> 4) ^ ++s_freeRandomByteCounter), - _uniqueAlivePathCount(0), - _localMultipathSupported(false), - _remoteMultipathSupported(false), - _canUseMultipath(false), _vProto(0), _vMajor(0), _vMinor(0), @@ -58,17 +49,17 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident _id(peerIdentity), _directPathPushCutoffCount(0), _credentialsCutoffCount(0), - _linkIsBalanced(false), - _linkIsRedundant(false), - _remotePeerMultipathEnabled(false), - _lastAggregateStatsReport(0), - _lastAggregateAllocation(0), - _virtualPathCount(0), - _roundRobinPathAssignmentIdx(0), - _pathAssignmentIdx(0) + _echoRequestCutoffCount(0), + _uniqueAlivePathCount(0), + _localMultipathSupported(false), + _remoteMultipathSupported(false), + _canUseMultipath(false), + _shouldCollectPathStatistics(0), + _lastComputedAggregateMeanLatency(0) { - if (!myIdentity.agree(peerIdentity,_key,ZT_PEER_SECRET_KEY_LENGTH)) + if (!myIdentity.agree(peerIdentity,_key,ZT_PEER_SECRET_KEY_LENGTH)) { throw ZT_EXCEPTION_INVALID_ARGUMENT; + } } void Peer::received( @@ -81,7 +72,8 @@ void Peer::received( const uint64_t inRePacketId, const Packet::Verb inReVerb, const bool trustEstablished, - const uint64_t networkId) + const uint64_t networkId, + const int32_t flowId) { const int64_t now = RR->node->now(); @@ -98,28 +90,13 @@ void Peer::received( break; } + recordIncomingPacket(tPtr, path, packetId, payloadLength, verb, flowId, now); + if (trustEstablished) { _lastTrustEstablishedPacketReceived = now; path->trustedPacketReceived(now); } - { - Mutex::Lock _l(_paths_m); - - recordIncomingPacket(tPtr, path, packetId, payloadLength, verb, now); - - if (_canUseMultipath) { - if (path->needsToSendQoS(now)) { - sendQOS_MEASUREMENT(tPtr, path, path->localSocket(), path->address(), now); - } - for(unsigned int i=0;iprocessBackgroundPathMeasurements(now); - } - } - } - } - if (hops == 0) { // If this is a direct packet (no hops), update existing paths or learn new ones bool havePath = false; @@ -137,60 +114,45 @@ void Peer::received( } bool attemptToContact = false; + + int replaceIdx = ZT_MAX_PEER_NETWORK_PATHS; if ((!havePath)&&(RR->node->shouldUsePathForZeroTierTraffic(tPtr,_id.address(),path->localSocket(),path->address()))) { Mutex::Lock _l(_paths_m); - - // Paths are redundant if they duplicate an alive path to the same IP or - // with the same local socket and address family. - bool redundant = false; - unsigned int replacePath = ZT_MAX_PEER_NETWORK_PATHS; for(unsigned int i=0;ialive(now)) && ( ((_paths[i].p->localSocket() == path->localSocket())&&(_paths[i].p->address().ss_family == path->address().ss_family)) || (_paths[i].p->address().ipsEqual2(path->address())) ) ) { - redundant = true; - break; - } - // If the path is the same address and port, simply assume this is a replacement - if ( (_paths[i].p->address().ipsEqual2(path->address()))) { - replacePath = i; - break; - } - } else break; - } - - // If the path isn't a duplicate of the same localSocket AND we haven't already determined a replacePath, - // then find the worst path and replace it. - if (!redundant && replacePath == ZT_MAX_PEER_NETWORK_PATHS) { - int replacePathQuality = 0; - for(unsigned int i=0;iquality(now); - if (q > replacePathQuality) { - replacePathQuality = q; - replacePath = i; + // match addr + if ( (_paths[i].p->alive(now)) && ( ((_paths[i].p->localSocket() == path->localSocket())&&(_paths[i].p->address().ss_family == path->address().ss_family)) && (_paths[i].p->address().ipsEqual2(path->address())) ) ) { + // port + if (_paths[i].p->address().port() == path->address().port()) { + replaceIdx = i; + break; } - } else { - replacePath = i; + } + } + } + if (replaceIdx == ZT_MAX_PEER_NETWORK_PATHS) { + for(unsigned int i=0;it->peerLearnedNewPath(tPtr,networkId,*this,path,packetId); - _paths[replacePath].lr = now; - _paths[replacePath].p = path; - _paths[replacePath].priority = 1; + performMultipathStateCheck(now); + if (_bondToPeer) { + _bondToPeer->nominatePath(path, now); + } + _paths[replaceIdx].lr = now; + _paths[replaceIdx].p = path; + _paths[replaceIdx].priority = 1; } else { attemptToContact = true; } - - // Every time we learn of new path, rebuild set of virtual paths - constructSetOfVirtualPaths(); } } - if (attemptToContact) { attemptToContactAt(tPtr,path->localSocket(),path->address(),now,true); path->sent(now); @@ -203,8 +165,7 @@ void Peer::received( // is done less frequently. if (this->trustEstablished(now)) { const int64_t sinceLastPush = now - _lastDirectPathPushSent; - if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH : ZT_DIRECT_PATH_PUSH_INTERVAL) - || (_localMultipathSupported && (sinceLastPush >= (ZT_DIRECT_PATH_PUSH_INTERVAL_MULTIPATH)))) { + if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH : ZT_DIRECT_PATH_PUSH_INTERVAL)) { _lastDirectPathPushSent = now; std::vector pathsToPush(RR->node->directPaths()); if (pathsToPush.size() > 0) { @@ -249,189 +210,15 @@ void Peer::received( } } -void Peer::constructSetOfVirtualPaths() +SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired, int32_t flowId) { - if (!_remoteMultipathSupported) { - return; - } - Mutex::Lock _l(_virtual_paths_m); - - int64_t now = RR->node->now(); - _virtualPathCount = 0; - for(unsigned int i=0;ialive(now)) { - for(unsigned int j=0;jalive(now)) { - int64_t localSocket = _paths[j].p->localSocket(); - bool foundVirtualPath = false; - for (int k=0; k<_virtualPaths.size(); k++) { - if (_virtualPaths[k]->localSocket == localSocket && _virtualPaths[k]->p == _paths[i].p) { - foundVirtualPath = true; - } - } - if (!foundVirtualPath) - { - VirtualPath *np = new VirtualPath; - np->p = _paths[i].p; - np->localSocket = localSocket; - _virtualPaths.push_back(np); - } - } - } - } - } -} - -void Peer::recordOutgoingPacket(const SharedPtr &path, const uint64_t packetId, - uint16_t payloadLength, const Packet::Verb verb, int64_t now) -{ - _freeRandomByte += (unsigned char)(packetId >> 8); // grab entropy to use in path selection logic for multipath - if (_canUseMultipath) { - path->recordOutgoingPacket(now, packetId, payloadLength, verb); - } -} - -void Peer::recordIncomingPacket(void *tPtr, const SharedPtr &path, const uint64_t packetId, - uint16_t payloadLength, const Packet::Verb verb, int64_t now) -{ - if (_canUseMultipath) { - if (path->needsToSendAck(now)) { - sendACK(tPtr, path, path->localSocket(), path->address(), now); - } - path->recordIncomingPacket(now, packetId, payloadLength, verb); - } -} - -void Peer::computeAggregateAllocation(int64_t now) -{ - float maxStability = 0; - float totalRelativeQuality = 0; - float maxThroughput = 1; - float maxScope = 0; - float relStability[ZT_MAX_PEER_NETWORK_PATHS]; - float relThroughput[ZT_MAX_PEER_NETWORK_PATHS]; - memset(&relStability, 0, sizeof(relStability)); - memset(&relThroughput, 0, sizeof(relThroughput)); - // Survey all paths - for(unsigned int i=0;ilastComputedStability(); - relThroughput[i] = (float)_paths[i].p->maxLifetimeThroughput(); - maxStability = relStability[i] > maxStability ? relStability[i] : maxStability; - maxThroughput = relThroughput[i] > maxThroughput ? relThroughput[i] : maxThroughput; - maxScope = _paths[i].p->ipScope() > maxScope ? _paths[i].p->ipScope() : maxScope; - } - } - // Convert to relative values - for(unsigned int i=0;iackAge(now), 0, ZT_PATH_MAX_AGE, 0, 10); - float age_contrib = exp((-1)*normalized_ma); - float relScope = ((float)(_paths[i].p->ipScope()+1) / (maxScope + 1)); - float relQuality = - (relStability[i] * (float)ZT_PATH_CONTRIB_STABILITY) - + (fmaxf(1.0f, relThroughput[i]) * (float)ZT_PATH_CONTRIB_THROUGHPUT) - + relScope * (float)ZT_PATH_CONTRIB_SCOPE; - relQuality *= age_contrib; - // Clamp values - relQuality = relQuality > (1.00f / 100.0f) ? relQuality : 0.0f; - relQuality = relQuality < (99.0f / 100.0f) ? relQuality : 1.0f; - totalRelativeQuality += relQuality; - _paths[i].p->updateRelativeQuality(relQuality); - } - } - // Convert set of relative performances into an allocation set - for(uint16_t i=0;inode->getMultipathMode() == ZT_MULTIPATH_BALANCE_RANDOM) { - _paths[i].p->updateComponentAllocationOfAggregateLink(((float)_pathChoiceHist.countValue(i) / (float)_pathChoiceHist.count()) * 255); - } - if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_DYNAMIC_OPAQUE) { - _paths[i].p->updateComponentAllocationOfAggregateLink((unsigned char)((_paths[i].p->relativeQuality() / totalRelativeQuality) * 255)); - } - } - } -} - -int Peer::computeAggregateLinkPacketDelayVariance() -{ - float pdv = 0.0; - for(unsigned int i=0;irelativeQuality() * _paths[i].p->packetDelayVariance(); - } - } - return (int)pdv; -} - -int Peer::computeAggregateLinkMeanLatency() -{ - int ml = 0; - int pathCount = 0; - for(unsigned int i=0;irelativeQuality() * _paths[i].p->meanLatency()); - } - } - return ml / pathCount; -} - -int Peer::aggregateLinkPhysicalPathCount() -{ - std::map ifnamemap; - int pathCount = 0; - int64_t now = RR->node->now(); - for(unsigned int i=0;ialive(now)) { - if (!ifnamemap[_paths[i].p->getName()]) { - ifnamemap[_paths[i].p->getName()] = true; - pathCount++; - } - } - } - return pathCount; -} - -int Peer::aggregateLinkLogicalPathCount() -{ - int pathCount = 0; - int64_t now = RR->node->now(); - for(unsigned int i=0;ialive(now)) { - pathCount++; - } - } - return pathCount; -} - -std::vector > Peer::getAllPaths(int64_t now) -{ - Mutex::Lock _l(_virtual_paths_m); // FIXME: TX can now lock RX - std::vector > paths; - for (int i=0; i<_virtualPaths.size(); i++) { - if (_virtualPaths[i]->p) { - paths.push_back(_virtualPaths[i]->p); - } - } - return paths; -} - -SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired, int64_t flowId) -{ - Mutex::Lock _l(_paths_m); - SharedPtr selectedPath; - char curPathStr[128]; - char newPathStr[128]; - unsigned int bestPath = ZT_MAX_PEER_NETWORK_PATHS; - - /** - * Send traffic across the highest quality path only. This algorithm will still - * use the old path quality metric from protocol version 9. - */ - if (!_canUseMultipath) { + if (!_bondToPeer) { + Mutex::Lock _l(_paths_m); + unsigned int bestPath = ZT_MAX_PEER_NETWORK_PATHS; + /** + * Send traffic across the highest quality path only. This algorithm will still + * use the old path quality metric from protocol version 9. + */ long bestPathQuality = 2147483647; for(unsigned int i=0;i Peer::getAppropriatePath(int64_t now, bool includeExpired, int64 } return SharedPtr(); } - - // Update path measurements - for(unsigned int i=0;iprocessBackgroundPathMeasurements(now); - } - } - if (RR->sw->isFlowAware()) { - // Detect new flows and update existing records - if (_flows.count(flowId)) { - _flows[flowId]->lastSend = now; - } - else { - fprintf(stderr, "new flow %llx detected between this node and %llx (%lu active flow(s))\n", - flowId, this->_id.address().toInt(), (_flows.size()+1)); - struct Flow *newFlow = new Flow(flowId, now); - _flows[flowId] = newFlow; - newFlow->assignedPath = nullptr; - } - } - // Construct set of virtual paths if needed - if (!_virtualPaths.size()) { - constructSetOfVirtualPaths(); - } - if (!_virtualPaths.size()) { - fprintf(stderr, "no paths to send packet out on\n"); - return SharedPtr(); - } - - /** - * All traffic is sent on all paths. - */ - if (RR->node->getMultipathMode() == ZT_MULTIPATH_BROADCAST) { - // Not handled here. Handled in Switch::_trySend() - } - - /** - * Only one link is active. Fail-over is immediate. - */ - if (RR->node->getMultipathMode() == ZT_MULTIPATH_ACTIVE_BACKUP) { - bool bFoundHotPath = false; - if (!_activeBackupPath) { - /* Select the fist path that appears to still be active. - * This will eventually be user-configurable */ - for (int i=0; ilastIn()) < ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD) { - bFoundHotPath = true; - _activeBackupPath = _paths[i].p; - _pathAssignmentIdx = i; - _activeBackupPath->address().toString(curPathStr); - fprintf(stderr, "selected %s as the primary active-backup path to %llx (idx=%d)\n", - curPathStr, this->_id.address().toInt(), _pathAssignmentIdx); - break; - } - } - } - } - else { - char what[128]; - if ((now - _activeBackupPath->lastIn()) > ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD) { - _activeBackupPath->address().toString(curPathStr); // Record path string for later debug trace - int16_t previousIdx = _pathAssignmentIdx; - SharedPtr nextAlternativePath; - // Search for a hot path, at the same time find the next path in - // a RR sequence that seems viable to use as an alternative - int searchCount = 0; - while (searchCount < ZT_MAX_PEER_NETWORK_PATHS) { - _pathAssignmentIdx++; - if (_pathAssignmentIdx == ZT_MAX_PEER_NETWORK_PATHS) { - _pathAssignmentIdx = 0; - } - searchCount++; - if (_paths[_pathAssignmentIdx].p) { - _paths[_pathAssignmentIdx].p->address().toString(what); - if (_activeBackupPath.ptr() == _paths[_pathAssignmentIdx].p.ptr()) { - continue; - } - if (!nextAlternativePath) { // Record the first viable alternative in the RR sequence - nextAlternativePath = _paths[_pathAssignmentIdx].p; - } - if ((now - _paths[_pathAssignmentIdx].p->lastIn()) < ZT_MULTIPATH_ACTIVE_BACKUP_RAPID_FAILOVER_PERIOD) { - bFoundHotPath = true; - _activeBackupPath = _paths[_pathAssignmentIdx].p; - _activeBackupPath->address().toString(newPathStr); - fprintf(stderr, "primary active-backup path %s to %llx appears to be dead, switched to %s\n", - curPathStr, this->_id.address().toInt(), newPathStr); - break; - } - } - } - if (!bFoundHotPath) { - if (nextAlternativePath) { - _activeBackupPath = nextAlternativePath; - _activeBackupPath->address().toString(curPathStr); - //fprintf(stderr, "no hot paths found to use as active-backup primary to %llx, using next best: %s\n", - // this->_id.address().toInt(), curPathStr); - } - else { - // No change - } - } - } - } - if (!_activeBackupPath) { - return SharedPtr(); - } - return _activeBackupPath; - } - - /** - * Traffic is randomly distributed among all active paths. - */ - if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_RANDOM) { - int sz = _virtualPaths.size(); - if (sz) { - int idx = _freeRandomByte % sz; - _pathChoiceHist.push(idx); - _virtualPaths[idx]->p->address().toString(curPathStr); - fprintf(stderr, "sending out: (%llx), idx=%d: path=%s, localSocket=%lld\n", - this->_id.address().toInt(), idx, curPathStr, _virtualPaths[idx]->localSocket); - return _virtualPaths[idx]->p; - } - // This call is algorithmically inert but gives us a value to show in the status output - computeAggregateAllocation(now); - } - - /** - * Packets are striped across all available paths. - */ - if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_RR_OPAQUE) { - int16_t previousIdx = _roundRobinPathAssignmentIdx; - int cycleCount = 0; - int minLastIn = 0; - int bestAlternativeIdx = -1; - while (cycleCount < ZT_MAX_PEER_NETWORK_PATHS) { - if (_roundRobinPathAssignmentIdx < (_virtualPaths.size()-1)) { - _roundRobinPathAssignmentIdx++; - } - else { - _roundRobinPathAssignmentIdx = 0; - } - cycleCount++; - if (_virtualPaths[_roundRobinPathAssignmentIdx]->p) { - uint64_t lastIn = _virtualPaths[_roundRobinPathAssignmentIdx]->p->lastIn(); - if (bestAlternativeIdx == -1) { - minLastIn = lastIn; // Initialization - bestAlternativeIdx = 0; - } - if (lastIn < minLastIn) { - minLastIn = lastIn; - bestAlternativeIdx = _roundRobinPathAssignmentIdx; - } - if ((now - lastIn) < 5000) { - selectedPath = _virtualPaths[_roundRobinPathAssignmentIdx]->p; - } - } - } - // If we can't find an appropriate path, try the most recently active one - if (!selectedPath) { - _roundRobinPathAssignmentIdx = bestAlternativeIdx; - selectedPath = _virtualPaths[bestAlternativeIdx]->p; - selectedPath->address().toString(curPathStr); - fprintf(stderr, "could not find good path, settling for next best %s\n",curPathStr); - } - selectedPath->address().toString(curPathStr); - fprintf(stderr, "sending packet out on path %s at index %d\n", - curPathStr, _roundRobinPathAssignmentIdx); - return selectedPath; - } - - /** - * Flows are striped across all available paths. - */ - if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_RR_FLOW) { - // fprintf(stderr, "ZT_MULTIPATH_BALANCE_RR_FLOW\n"); - } - - /** - * Flows are hashed across all available paths. - */ - if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_XOR_FLOW) { - // fprintf(stderr, "ZT_MULTIPATH_BALANCE_XOR_FLOW (%llx) \n", flowId); - struct Flow *currFlow = NULL; - if (_flows.count(flowId)) { - currFlow = _flows[flowId]; - if (!currFlow->assignedPath) { - int idx = abs((int)(currFlow->flowId % (_virtualPaths.size()-1))); - currFlow->assignedPath = _virtualPaths[idx]; - _virtualPaths[idx]->p->address().toString(curPathStr); - fprintf(stderr, "assigning flow %llx between this node and peer %llx to path %s at index %d\n", - currFlow->flowId, this->_id.address().toInt(), curPathStr, idx); - } - else { - if (!currFlow->assignedPath->p->alive(now)) { - currFlow->assignedPath->p->address().toString(curPathStr); - // Re-assign - int idx = abs((int)(currFlow->flowId % (_virtualPaths.size()-1))); - currFlow->assignedPath = _virtualPaths[idx]; - _virtualPaths[idx]->p->address().toString(newPathStr); - fprintf(stderr, "path %s assigned to flow %llx between this node and %llx appears to be dead, reassigning to path %s\n", - curPathStr, currFlow->flowId, this->_id.address().toInt(), newPathStr); - } - } - return currFlow->assignedPath->p; - } - } - - /** - * Proportionally allocate traffic according to dynamic path quality measurements. - */ - if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_DYNAMIC_OPAQUE) { - if ((now - _lastAggregateAllocation) >= ZT_PATH_QUALITY_COMPUTE_INTERVAL) { - _lastAggregateAllocation = now; - computeAggregateAllocation(now); - } - // Randomly choose path according to their allocations - float rf = _freeRandomByte; - for(int i=0;iallocation()) { - bestPath = i; - _pathChoiceHist.push(bestPath); // Record which path we chose - break; - } - rf -= _paths[i].p->allocation(); - } - } - if (bestPath < ZT_MAX_PEER_NETWORK_PATHS) { - return _paths[bestPath].p; - } - } - - /** - * Flows are dynamically allocated across paths in proportion to link strength and load. - */ - if (RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_DYNAMIC_FLOW) { - } - - return SharedPtr(); -} - -char *Peer::interfaceListStr() -{ - std::map ifnamemap; - char tmp[32]; - const int64_t now = RR->node->now(); - char *ptr = _interfaceListStr; - bool imbalanced = false; - memset(_interfaceListStr, 0, sizeof(_interfaceListStr)); - int alivePathCount = aggregateLinkLogicalPathCount(); - for(unsigned int i=0;ialive(now)) { - int ipv = _paths[i].p->address().isV4(); - // If this is acting as an aggregate link, check allocations - float targetAllocation = 1.0f / (float)alivePathCount; - float currentAllocation = 1.0f; - if (alivePathCount > 1) { - currentAllocation = (float)_pathChoiceHist.countValue(i) / (float)_pathChoiceHist.count(); - if (fabs(targetAllocation - currentAllocation) > ZT_PATH_IMBALANCE_THRESHOLD) { - imbalanced = true; - } - } - char *ipvStr = ipv ? (char*)"ipv4" : (char*)"ipv6"; - sprintf(tmp, "(%s, %s, %.3f)", _paths[i].p->getName(), ipvStr, currentAllocation); - // Prevent duplicates - if(ifnamemap[_paths[i].p->getName()] != ipv) { - memcpy(ptr, tmp, strlen(tmp)); - ptr += strlen(tmp); - *ptr = ' '; - ptr++; - ifnamemap[_paths[i].p->getName()] = ipv; - } - } - } - ptr--; // Overwrite trailing space - if (imbalanced) { - sprintf(tmp, ", is asymmetrical"); - memcpy(ptr, tmp, sizeof(tmp)); - } else { - *ptr = '\0'; - } - return _interfaceListStr; + return _bondToPeer->getAppropriatePath(now, flowId); } void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &other) const @@ -859,87 +360,6 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o } } -inline void Peer::processBackgroundPeerTasks(const int64_t now) -{ - // Determine current multipath compatibility with other peer - if ((now - _lastMultipathCompatibilityCheck) >= ZT_PATH_QUALITY_COMPUTE_INTERVAL) { - // - // Cache number of available paths so that we can short-circuit multipath logic elsewhere - // - // We also take notice of duplicate paths (same IP only) because we may have - // recently received a direct path push from a peer and our list might contain - // a dead path which hasn't been fully recognized as such. In this case we - // don't want the duplicate to trigger execution of multipath code prematurely. - // - // This is done to support the behavior of auto multipath enable/disable - // without user intervention. - // - int currAlivePathCount = 0; - int duplicatePathsFound = 0; - for (unsigned int i=0;iaddress().ipsEqual2(_paths[j].p->address()) && i != j) { - duplicatePathsFound+=1; - break; - } - } - } - } - _uniqueAlivePathCount = (currAlivePathCount - (duplicatePathsFound / 2)); - _lastMultipathCompatibilityCheck = now; - _localMultipathSupported = ((RR->node->getMultipathMode() != ZT_MULTIPATH_NONE) && (ZT_PROTO_VERSION > 9)); - _remoteMultipathSupported = _vProto > 9; - // If both peers support multipath and more than one path exist, we can use multipath logic - _canUseMultipath = _localMultipathSupported && _remoteMultipathSupported && (_uniqueAlivePathCount > 1); - } - - // Remove old flows - if (RR->sw->isFlowAware()) { - std::map::iterator it = _flows.begin(); - while (it != _flows.end()) { - if ((now - it->second->lastSend) > ZT_MULTIPATH_FLOW_EXPIRATION) { - fprintf(stderr, "forgetting flow %llx between this node and %llx (%lu active flow(s))\n", - it->first, this->_id.address().toInt(), _flows.size()); - it = _flows.erase(it); - } else { - it++; - } - } - } -} - -void Peer::sendACK(void *tPtr,const SharedPtr &path,const int64_t localSocket,const InetAddress &atAddress,int64_t now) -{ - Packet outp(_id.address(),RR->identity.address(),Packet::VERB_ACK); - uint32_t bytesToAck = path->bytesToAck(); - outp.append(bytesToAck); - if (atAddress) { - outp.armor(_key,false); - RR->node->putPacket(tPtr,localSocket,atAddress,outp.data(),outp.size()); - } else { - RR->sw->send(tPtr,outp,false); - } - path->sentAck(now); -} - -void Peer::sendQOS_MEASUREMENT(void *tPtr,const SharedPtr &path,const int64_t localSocket,const InetAddress &atAddress,int64_t now) -{ - const int64_t _now = RR->node->now(); - Packet outp(_id.address(),RR->identity.address(),Packet::VERB_QOS_MEASUREMENT); - char qosData[ZT_PATH_MAX_QOS_PACKET_SZ]; - int16_t len = path->generateQoSPacket(_now,qosData); - outp.append(qosData,len); - if (atAddress) { - outp.armor(_key,false); - RR->node->putPacket(tPtr,localSocket,atAddress,outp.data(),outp.size()); - } else { - RR->sw->send(tPtr,outp,false); - } - path->sentQoS(now); -} - void Peer::sendHELLO(void *tPtr,const int64_t localSocket,const InetAddress &atAddress,int64_t now) { Packet outp(_id.address(),RR->identity.address(),Packet::VERB_HELLO); @@ -1005,29 +425,57 @@ void Peer::tryMemorizedPath(void *tPtr,int64_t now) } } +void Peer::performMultipathStateCheck(int64_t now) +{ + /** + * Check for conditions required for multipath bonding and create a bond + * if allowed. + */ + _localMultipathSupported = ((RR->bc->inUse()) && (ZT_PROTO_VERSION > 9)); + if (_localMultipathSupported) { + int currAlivePathCount = 0; + int duplicatePathsFound = 0; + for (unsigned int i=0;iaddress().ipsEqual2(_paths[j].p->address()) && i != j) { + duplicatePathsFound+=1; + break; + } + } + } + } + _uniqueAlivePathCount = (currAlivePathCount - (duplicatePathsFound / 2)); + _remoteMultipathSupported = _vProto > 9; + _canUseMultipath = _localMultipathSupported && _remoteMultipathSupported && (_uniqueAlivePathCount > 1); + } + if (_canUseMultipath && !_bondToPeer) { + if (RR->bc) { + _bondToPeer = RR->bc->createTransportTriggeredBond(RR, this); + /** + * Allow new bond to retroactively learn all paths known to this peer + */ + if (_bondToPeer) { + for (unsigned int i=0;inominatePath(_paths[i].p, now); + } + } + } + } + } +} + unsigned int Peer::doPingAndKeepalive(void *tPtr,int64_t now) { unsigned int sent = 0; Mutex::Lock _l(_paths_m); - processBackgroundPeerTasks(now); + performMultipathStateCheck(now); - // Emit traces regarding aggregate link status - if (_canUseMultipath) { - int alivePathCount = aggregateLinkPhysicalPathCount(); - if ((now - _lastAggregateStatsReport) > ZT_PATH_AGGREGATE_STATS_REPORT_INTERVAL) { - _lastAggregateStatsReport = now; - if (alivePathCount) { - RR->t->peerLinkAggregateStatistics(NULL,*this); - } - } if (alivePathCount < 2 && _linkIsRedundant) { - _linkIsRedundant = !_linkIsRedundant; - RR->t->peerLinkNoLongerAggregate(NULL,*this); - } if (alivePathCount > 1 && !_linkIsRedundant) { - _linkIsRedundant = !_linkIsRedundant; - RR->t->peerLinkNoLongerAggregate(NULL,*this); - } - } + const bool sendFullHello = ((now - _lastSentFullHello) >= ZT_PEER_PING_PERIOD); + _lastSentFullHello = now; // Right now we only keep pinging links that have the maximum priority. The // priority is used to track cluster redirections, meaning that when a cluster @@ -1040,15 +488,13 @@ unsigned int Peer::doPingAndKeepalive(void *tPtr,int64_t now) else break; } - const bool sendFullHello = ((now - _lastSentFullHello) >= ZT_PEER_PING_PERIOD); - _lastSentFullHello = now; - unsigned int j = 0; for(unsigned int i=0;ineedsHeartbeat(now))) { + if ((sendFullHello)||(_paths[i].p->needsHeartbeat(now)) + || (_canUseMultipath && _paths[i].p->needsGratuitousHeartbeat(now))) { attemptToContactAt(tPtr,_paths[i].p->localSocket(),_paths[i].p->address(),now,sendFullHello); _paths[i].p->sent(now); sent |= (_paths[i].p->address().ss_family == AF_INET) ? 0x1 : 0x2; @@ -1059,14 +505,6 @@ unsigned int Peer::doPingAndKeepalive(void *tPtr,int64_t now) } } else break; } - if (canUseMultipath()) { - while(j < ZT_MAX_PEER_NETWORK_PATHS) { - _paths[j].lr = 0; - _paths[j].p.zero(); - _paths[j].priority = 1; - ++j; - } - } return sent; } @@ -1133,4 +571,30 @@ void Peer::resetWithinScope(void *tPtr,InetAddress::IpScope scope,int inetAddres } } +void Peer::recordOutgoingPacket(const SharedPtr &path, const uint64_t packetId, + uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now) +{ + if (!_shouldCollectPathStatistics || !_bondToPeer) { + return; + } + _bondToPeer->recordOutgoingPacket(path, packetId, payloadLength, verb, flowId, now); +} + +void Peer::recordIncomingInvalidPacket(const SharedPtr& path) +{ + if (!_shouldCollectPathStatistics || !_bondToPeer) { + return; + } + _bondToPeer->recordIncomingInvalidPacket(path); +} + +void Peer::recordIncomingPacket(void *tPtr, const SharedPtr &path, const uint64_t packetId, + uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now) +{ + if (!_shouldCollectPathStatistics || !_bondToPeer) { + return; + } + _bondToPeer->recordIncomingPacket(path, packetId, payloadLength, verb, flowId, now); +} + } // namespace ZeroTier diff --git a/node/Peer.hpp b/node/Peer.hpp index ef4645e9a..1a2b6abc1 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -15,8 +15,6 @@ #define ZT_PEER_HPP #include -#include -#include #include "../include/ZeroTierOne.h" @@ -33,6 +31,8 @@ #include "AtomicCounter.hpp" #include "Hashtable.hpp" #include "Mutex.hpp" +#include "Bond.hpp" +#include "BondController.hpp" #define ZT_PEER_MAX_SERIALIZED_STATE_SIZE (sizeof(Peer) + 32 + (sizeof(Path) * 2)) @@ -44,6 +44,9 @@ namespace ZeroTier { class Peer { friend class SharedPtr; + friend class SharedPtr; + friend class Switch; + friend class Bond; private: Peer() {} // disabled to prevent bugs -- should not be constructed uninitialized @@ -97,7 +100,8 @@ public: const uint64_t inRePacketId, const Packet::Verb inReVerb, const bool trustEstablished, - const uint64_t networkId); + const uint64_t networkId, + const int32_t flowId); /** * Check whether we have an active path to this peer via the given address @@ -136,94 +140,49 @@ public: return false; } - void constructSetOfVirtualPaths(); - /** - * Record statistics on outgoing packets - * - * @param path Path over which packet was sent - * @param id Packet ID - * @param len Length of packet payload - * @param verb Packet verb - * @param now Current time - */ - void recordOutgoingPacket(const SharedPtr &path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, int64_t now); - - /** - * Record statistics on incoming packets - * - * @param path Path over which packet was sent - * @param id Packet ID - * @param len Length of packet payload - * @param verb Packet verb - * @param now Current time - */ - void recordIncomingPacket(void *tPtr, const SharedPtr &path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, int64_t now); - - /** - * Send an ACK to peer for the most recent packets received + * Record incoming packets to * * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call - * @param localSocket Raw socket the ACK packet will be sent over - * @param atAddress Destination for the ACK packet + * @param path Path over which packet was received + * @param packetId Packet ID + * @param payloadLength Length of packet data payload + * @param verb Packet verb + * @param flowId Flow ID * @param now Current time */ - void sendACK(void *tPtr, const SharedPtr &path, const int64_t localSocket,const InetAddress &atAddress,int64_t now); + void recordIncomingPacket(void *tPtr, const SharedPtr &path, const uint64_t packetId, + uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now); /** - * Send a QoS packet to peer so that it can evaluate the quality of this link * - * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call - * @param localSocket Raw socket the QoS packet will be sent over - * @param atAddress Destination for the QoS packet + * @param path Path over which packet is being sent + * @param packetId Packet ID + * @param payloadLength Length of packet data payload + * @param verb Packet verb + * @param flowId Flow ID * @param now Current time */ - void sendQOS_MEASUREMENT(void *tPtr, const SharedPtr &path, const int64_t localSocket,const InetAddress &atAddress,int64_t now); + void recordOutgoingPacket(const SharedPtr &path, const uint64_t packetId, + uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now); /** - * Compute relative quality values and allocations for the components of the aggregate link + * Record an invalid incoming packet. This packet failed + * MAC/compression/cipher checks and will now contribute to a + * Packet Error Ratio (PER). * - * @param now Current time + * @param path Path over which packet was received */ - void computeAggregateAllocation(int64_t now); - - /** - * @return The aggregate link Packet Delay Variance (PDV) - */ - int computeAggregateLinkPacketDelayVariance(); - - /** - * @return The aggregate link mean latency - */ - int computeAggregateLinkMeanLatency(); - - /** - * @return The number of currently alive "physical" paths in the aggregate link - */ - int aggregateLinkPhysicalPathCount(); - - /** - * @return The number of currently alive "logical" paths in the aggregate link - */ - int aggregateLinkLogicalPathCount(); - - std::vector> getAllPaths(int64_t now); + void recordIncomingInvalidPacket(const SharedPtr& path); /** * Get the most appropriate direct path based on current multipath and QoS configuration * * @param now Current time - * @param flowId Session-specific protocol flow identifier used for path allocation * @param includeExpired If true, include even expired paths * @return Best current path or NULL if none */ - SharedPtr getAppropriatePath(int64_t now, bool includeExpired, int64_t flowId = -1); - - /** - * Generate a human-readable string of interface names making up the aggregate link, also include - * moving allocation and IP version number for each (for tracing) - */ - char *interfaceListStr(); + SharedPtr getAppropriatePath(int64_t now, bool includeExpired, int32_t flowId = -1); /** * Send VERB_RENDEZVOUS to this and another peer via the best common IP scope and path @@ -265,6 +224,13 @@ public: */ void tryMemorizedPath(void *tPtr,int64_t now); + /** + * A check to be performed periodically which determines whether multipath communication is + * possible with this peer. This check should be performed early in the life-cycle of the peer + * as well as during the process of learning new paths. + */ + void performMultipathStateCheck(int64_t now); + /** * Send pings or keepalives depending on configured timeouts * @@ -277,16 +243,6 @@ public: */ unsigned int doPingAndKeepalive(void *tPtr,int64_t now); - /** - * Clear paths whose localSocket(s) are in a CLOSED state or have an otherwise INVALID state. - * This should be called frequently so that we can detect and remove unproductive or invalid paths. - * - * Under the hood this is done periodically based on ZT_CLOSED_PATH_PRUNING_INTERVAL. - * - * @return Number of paths that were pruned this round - */ - unsigned int prunePaths(); - /** * Process a cluster redirect sent by this peer * @@ -348,7 +304,7 @@ public: inline unsigned int latency(const int64_t now) { if (_canUseMultipath) { - return (int)computeAggregateLinkMeanLatency(); + return (int)_lastComputedAggregateMeanLatency; } else { SharedPtr bp(getAppropriatePath(now,false)); if (bp) @@ -407,37 +363,6 @@ public: inline bool remoteVersionKnown() const { return ((_vMajor > 0)||(_vMinor > 0)||(_vRevision > 0)); } - /** - * Periodically update known multipath activation constraints. This is done so that we know when and when - * not to use multipath logic. Doing this once every few seconds is sufficient. - * - * @param now Current time - */ - inline void processBackgroundPeerTasks(const int64_t now); - - /** - * Record that the remote peer does have multipath enabled. As is evident by the receipt of a VERB_ACK - * or a VERB_QOS_MEASUREMENT packet at some point in the past. Until this flag is set, the local client - * shall assume that multipath is not enabled and should only use classical Protocol 9 logic. - */ - inline void inferRemoteMultipathEnabled() { _remotePeerMultipathEnabled = true; } - - /** - * @return Whether the local client supports and is configured to use multipath - */ - inline bool localMultipathSupport() { return _localMultipathSupported; } - - /** - * @return Whether the remote peer supports and is configured to use multipath - */ - inline bool remoteMultipathSupport() { return _remoteMultipathSupported; } - - /** - * @return Whether this client can use multipath to communicate with this peer. True if both peers are using - * the correct protocol and if both peers have multipath enabled. False if otherwise. - */ - inline bool canUseMultipath() { return _canUseMultipath; } - /** * @return True if peer has received a trust established packet (e.g. common network membership) in the past ZT_TRUST_EXPIRATION ms */ @@ -492,50 +417,35 @@ public: } /** - * Rate limit gate for inbound ECHO requests + * Rate limit gate for inbound ECHO requests. This rate limiter works + * by draining a certain number of requests per unit time. Each peer may + * theoretically receive up to ZT_ECHO_CUTOFF_LIMIT requests per second. */ inline bool rateGateEchoRequest(const int64_t now) { - if ((now - _lastEchoRequestReceived) >= ZT_PEER_GENERAL_RATE_LIMIT) { - _lastEchoRequestReceived = now; - return true; - } - return false; - } - - /** - * Rate limit gate for VERB_ACK - */ - inline bool rateGateACK(const int64_t now) - { - if ((now - _lastACKWindowReset) >= ZT_PATH_QOS_ACK_CUTOFF_TIME) { - _lastACKWindowReset = now; - _ACKCutoffCount = 0; + /* + // TODO: Rethink this + if (_canUseMultipath) { + _echoRequestCutoffCount++; + int numToDrain = (now - _lastEchoCheck) / ZT_ECHO_DRAINAGE_DIVISOR; + _lastEchoCheck = now; + fprintf(stderr, "ZT_ECHO_CUTOFF_LIMIT=%d, (now - _lastEchoCheck)=%d, numToDrain=%d, ZT_ECHO_DRAINAGE_DIVISOR=%d\n", ZT_ECHO_CUTOFF_LIMIT, (now - _lastEchoCheck), numToDrain, ZT_ECHO_DRAINAGE_DIVISOR); + if (_echoRequestCutoffCount > numToDrain) { + _echoRequestCutoffCount-=numToDrain; + } + else { + _echoRequestCutoffCount = 0; + } + return (_echoRequestCutoffCount < ZT_ECHO_CUTOFF_LIMIT); } else { - ++_ACKCutoffCount; + if ((now - _lastEchoRequestReceived) >= (ZT_PEER_GENERAL_RATE_LIMIT)) { + _lastEchoRequestReceived = now; + return true; + } + return false; } - return (_ACKCutoffCount < ZT_PATH_QOS_ACK_CUTOFF_LIMIT); - } - - /** - * Rate limit gate for VERB_QOS_MEASUREMENT - */ - inline bool rateGateQoS(const int64_t now) - { - if ((now - _lastQoSWindowReset) >= ZT_PATH_QOS_ACK_CUTOFF_TIME) { - _lastQoSWindowReset = now; - _QoSCutoffCount = 0; - } else { - ++_QoSCutoffCount; - } - return (_QoSCutoffCount < ZT_PATH_QOS_ACK_CUTOFF_LIMIT); - } - - /** - * @return Whether this peer is reachable via an aggregate link - */ - inline bool hasAggregateLink() { - return _localMultipathSupported && _remoteMultipathSupported && _remotePeerMultipathEnabled; + */ + return true; } /** @@ -610,6 +520,18 @@ public: } } + /** + * + * @return + */ + SharedPtr bond() { return _bondToPeer; } + + /** + * + * @return + */ + inline int8_t bondingPolicy() { return _bondingPolicy; } + private: struct _PeerPath { @@ -628,25 +550,16 @@ private: int64_t _lastTriedMemorizedPath; int64_t _lastDirectPathPushSent; int64_t _lastDirectPathPushReceive; + int64_t _lastEchoRequestReceived; int64_t _lastCredentialRequestSent; int64_t _lastWhoisRequestReceived; - int64_t _lastEchoRequestReceived; int64_t _lastCredentialsReceived; int64_t _lastTrustEstablishedPacketReceived; int64_t _lastSentFullHello; - int64_t _lastPathPrune; - int64_t _lastACKWindowReset; - int64_t _lastQoSWindowReset; - int64_t _lastMultipathCompatibilityCheck; + int64_t _lastEchoCheck; unsigned char _freeRandomByte; - int _uniqueAlivePathCount; - - bool _localMultipathSupported; - bool _remoteMultipathSupported; - bool _canUseMultipath; - uint16_t _vProto; uint16_t _vMajor; uint16_t _vMinor; @@ -659,62 +572,22 @@ private: unsigned int _directPathPushCutoffCount; unsigned int _credentialsCutoffCount; - unsigned int _QoSCutoffCount; - unsigned int _ACKCutoffCount; + unsigned int _echoRequestCutoffCount; AtomicCounter __refCount; - RingBuffer _pathChoiceHist; - - bool _linkIsBalanced; - bool _linkIsRedundant; bool _remotePeerMultipathEnabled; + int _uniqueAlivePathCount; + bool _localMultipathSupported; + bool _remoteMultipathSupported; + bool _canUseMultipath; - int64_t _lastAggregateStatsReport; - int64_t _lastAggregateAllocation; + volatile bool _shouldCollectPathStatistics; + volatile int8_t _bondingPolicy; - char _interfaceListStr[256]; // 16 characters * 16 paths in a link + int32_t _lastComputedAggregateMeanLatency; - // - struct LinkPerformanceEntry - { - int64_t packetId; - struct VirtualPath *egressVirtualPath; - struct VirtualPath *ingressVirtualPath; - }; - - // Virtual paths - int _virtualPathCount; - Mutex _virtual_paths_m; - struct VirtualPath - { - SharedPtr p; - int64_t localSocket; - std::queue performanceEntries; - }; - std::vector _virtualPaths; - - // Flows - struct Flow - { - Flow(int64_t fid, int64_t ls) : - flowId(fid), - lastSend(ls), - assignedPath(NULL) - {} - - int64_t flowId; - int64_t bytesPerSecond; - int64_t lastSend; - struct VirtualPath *assignedPath; - }; - - std::map _flows; - - int16_t _roundRobinPathAssignmentIdx; - - SharedPtr _activeBackupPath; - int16_t _pathAssignmentIdx; + SharedPtr _bondToPeer; }; } // namespace ZeroTier diff --git a/node/RingBuffer.hpp b/node/RingBuffer.hpp index 2d6cd1949..42047a873 100644 --- a/node/RingBuffer.hpp +++ b/node/RingBuffer.hpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -238,6 +238,21 @@ public: return curr_cnt ? subtotal / (float)curr_cnt : 0; } + /** + * @return The sum of the contents of the buffer + */ + inline float sum() + { + size_t iterator = begin; + float total = 0; + size_t curr_cnt = count(); + for (size_t i=0; i frameLen) + return false; // invalid! + proto = frameData[pos]; + pos += ((unsigned int)frameData[pos + 1] * 8) + 8; + break; + + //case 44: // fragment -- we currently can't parse these and they are deprecated in IPv6 anyway + //case 50: + //case 51: // IPSec ESP and AH -- we have to stop here since this is encrypted stuff + default: + return true; + } + } + return false; // overflow == invalid +} + void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddress &fromAddr,const void *data,unsigned int len) { + int32_t flowId = ZT_QOS_NO_FLOW; try { const int64_t now = RR->node->now(); @@ -112,6 +142,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre if (rq->packetId != fragmentPacketId) { // No packet found, so we received a fragment without its head. + rq->flowId = flowId; rq->timestamp = now; rq->packetId = fragmentPacketId; rq->frags[fragmentNumber - 1] = fragment; @@ -130,7 +161,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre for(unsigned int f=1;ffrag0.append(rq->frags[f - 1].payload(),rq->frags[f - 1].payloadLength()); - if (rq->frag0.tryDecode(RR,tPtr)) { + if (rq->frag0.tryDecode(RR,tPtr,flowId)) { rq->timestamp = 0; // packet decoded, free entry } else { rq->complete = true; // set complete flag but leave entry since it probably needs WHOIS or something @@ -195,6 +226,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre if (rq->packetId != packetId) { // If we have no other fragments yet, create an entry and save the head + rq->flowId = flowId; rq->timestamp = now; rq->packetId = packetId; rq->frag0.init(data,len,path,now); @@ -211,7 +243,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre for(unsigned int f=1;ftotalFragments;++f) rq->frag0.append(rq->frags[f - 1].payload(),rq->frags[f - 1].payloadLength()); - if (rq->frag0.tryDecode(RR,tPtr)) { + if (rq->frag0.tryDecode(RR,tPtr,flowId)) { rq->timestamp = 0; // packet decoded, free entry } else { rq->complete = true; // set complete flag but leave entry since it probably needs WHOIS or something @@ -224,9 +256,10 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre } else { // Packet is unfragmented, so just process it IncomingPacket packet(data,len,path,now); - if (!packet.tryDecode(RR,tPtr)) { + if (!packet.tryDecode(RR,tPtr,flowId)) { RXQueueEntry *const rq = _nextRXQueueEntry(); Mutex::Lock rql(rq->lock); + rq->flowId = flowId; rq->timestamp = now; rq->packetId = packet.packetId(); rq->frag0 = packet; @@ -242,43 +275,6 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre } catch ( ... ) {} // sanity check, should be caught elsewhere } -// Returns true if packet appears valid; pos and proto will be set -static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsigned int &pos,unsigned int &proto) -{ - if (frameLen < 40) - return false; - pos = 40; - proto = frameData[6]; - while (pos <= frameLen) { - switch(proto) { - case 0: // hop-by-hop options - case 43: // routing - case 60: // destination options - case 135: // mobility options - if ((pos + 8) > frameLen) - return false; // invalid! - proto = frameData[pos]; - pos += ((unsigned int)frameData[pos + 1] * 8) + 8; - break; - - //case 44: // fragment -- we currently can't parse these and they are deprecated in IPv6 anyway - //case 50: - //case 51: // IPSec ESP and AH -- we have to stop here since this is encrypted stuff - default: - return true; - } - } - return false; // overflow == invalid -} - -bool Switch::isFlowAware() -{ - int mode = RR->node->getMultipathMode(); - return (( mode == ZT_MULTIPATH_BALANCE_RR_FLOW) - || (mode == ZT_MULTIPATH_BALANCE_XOR_FLOW) - || (mode == ZT_MULTIPATH_BALANCE_DYNAMIC_FLOW)); -} - void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) { if (!network->hasConfig()) @@ -293,75 +289,73 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const } } - uint8_t qosBucket = ZT_QOS_DEFAULT_BUCKET; + uint8_t qosBucket = ZT_AQM_DEFAULT_BUCKET; - /* A pseudo-unique identifier used by the balancing and bonding policies to associate properties - * of a specific protocol flow over time and to determine which virtual path this packet - * shall be sent out on. This identifier consists of the source port and destination port - * of the encapsulated frame. + /** + * A pseudo-unique identifier used by balancing and bonding policies to + * categorize individual flows/conversations for assignment to a specific + * physical path. This identifier consists of the source port and + * destination port of the encapsulated frame. * - * A flowId of -1 will indicate that whatever packet we are about transmit has no - * preferred virtual path and will be sent out according to what the multipath logic - * deems appropriate. An example of this would be an ICMP packet. + * A flowId of -1 will indicate that there is no preference for how this + * packet shall be sent. An example of this would be an ICMP packet. */ - int64_t flowId = -1; + int32_t flowId = ZT_QOS_NO_FLOW; - if (isFlowAware()) { - if (etherType == ZT_ETHERTYPE_IPV4 && (len >= 20)) { - uint16_t srcPort = 0; - uint16_t dstPort = 0; - int8_t proto = (reinterpret_cast(data)[9]); - const unsigned int headerLen = 4 * (reinterpret_cast(data)[0] & 0xf); - switch(proto) { - case 0x01: // ICMP - flowId = 0x01; - break; - // All these start with 16-bit source and destination port in that order - case 0x06: // TCP - case 0x11: // UDP - case 0x84: // SCTP - case 0x88: // UDPLite - if (len > (headerLen + 4)) { - unsigned int pos = headerLen + 0; - srcPort = (reinterpret_cast(data)[pos++]) << 8; - srcPort |= (reinterpret_cast(data)[pos]); - pos++; - dstPort = (reinterpret_cast(data)[pos++]) << 8; - dstPort |= (reinterpret_cast(data)[pos]); - flowId = ((int64_t)srcPort << 48) | ((int64_t)dstPort << 32) | proto; - } - break; - } + if (etherType == ZT_ETHERTYPE_IPV4 && (len >= 20)) { + uint16_t srcPort = 0; + uint16_t dstPort = 0; + uint8_t proto = (reinterpret_cast(data)[9]); + const unsigned int headerLen = 4 * (reinterpret_cast(data)[0] & 0xf); + switch(proto) { + case 0x01: // ICMP + //flowId = 0x01; + break; + // All these start with 16-bit source and destination port in that order + case 0x06: // TCP + case 0x11: // UDP + case 0x84: // SCTP + case 0x88: // UDPLite + if (len > (headerLen + 4)) { + unsigned int pos = headerLen + 0; + srcPort = (reinterpret_cast(data)[pos++]) << 8; + srcPort |= (reinterpret_cast(data)[pos]); + pos++; + dstPort = (reinterpret_cast(data)[pos++]) << 8; + dstPort |= (reinterpret_cast(data)[pos]); + flowId = dstPort ^ srcPort ^ proto; + } + break; } + } - if (etherType == ZT_ETHERTYPE_IPV6 && (len >= 40)) { - uint16_t srcPort = 0; - uint16_t dstPort = 0; - unsigned int pos; - unsigned int proto; - _ipv6GetPayload((const uint8_t *)data, len, pos, proto); - switch(proto) { - case 0x3A: // ICMPv6 - flowId = 0x3A; - break; - // All these start with 16-bit source and destination port in that order - case 0x06: // TCP - case 0x11: // UDP - case 0x84: // SCTP - case 0x88: // UDPLite - if (len > (pos + 4)) { - srcPort = (reinterpret_cast(data)[pos++]) << 8; - srcPort |= (reinterpret_cast(data)[pos]); - pos++; - dstPort = (reinterpret_cast(data)[pos++]) << 8; - dstPort |= (reinterpret_cast(data)[pos]); - flowId = ((int64_t)srcPort << 48) | ((int64_t)dstPort << 32) | proto; - } - break; - default: - break; - } + if (etherType == ZT_ETHERTYPE_IPV6 && (len >= 40)) { + uint16_t srcPort = 0; + uint16_t dstPort = 0; + unsigned int pos; + unsigned int proto; + _ipv6GetPayload((const uint8_t *)data, len, pos, proto); + switch(proto) { + case 0x3A: // ICMPv6 + //flowId = 0x3A; + break; + // All these start with 16-bit source and destination port in that order + case 0x06: // TCP + case 0x11: // UDP + case 0x84: // SCTP + case 0x88: // UDPLite + if (len > (pos + 4)) { + srcPort = (reinterpret_cast(data)[pos++]) << 8; + srcPort |= (reinterpret_cast(data)[pos]); + pos++; + dstPort = (reinterpret_cast(data)[pos++]) << 8; + dstPort |= (reinterpret_cast(data)[pos]); + flowId = dstPort ^ srcPort ^ proto; + } + break; + default: + break; } } @@ -595,7 +589,7 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const } } -void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet &packet,bool encrypt,int qosBucket,int64_t flowId) +void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet &packet,bool encrypt,int qosBucket,int32_t flowId) { if(!network->qosEnabled()) { send(tPtr, packet, encrypt, flowId); @@ -603,18 +597,16 @@ void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet & } NetworkQoSControlBlock *nqcb = _netQueueControlBlock[network->id()]; if (!nqcb) { - // DEBUG_INFO("creating network QoS control block (NQCB) for network %llx", network->id()); nqcb = new NetworkQoSControlBlock(); _netQueueControlBlock[network->id()] = nqcb; // Initialize ZT_QOS_NUM_BUCKETS queues and place them in the INACTIVE list // These queues will be shuffled between the new/old/inactive lists by the enqueue/dequeue algorithm - for (int i=0; iinactiveQueues.push_back(new ManagedQueue(i)); } } // Don't apply QoS scheduling to ZT protocol traffic if (packet.verb() != Packet::VERB_FRAME && packet.verb() != Packet::VERB_EXT_FRAME) { - // just send packet normally, no QoS for ZT protocol traffic send(tPtr, packet, encrypt, flowId); } @@ -624,8 +616,9 @@ void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet & const Address dest(packet.destination()); TXQueueEntry *txEntry = new TXQueueEntry(dest,RR->node->now(),packet,encrypt,flowId); + ManagedQueue *selectedQueue = nullptr; - for (size_t i=0; ioldQueues.size()) { // search old queues first (I think this is best since old would imply most recent usage of the queue) if (nqcb->oldQueues[i]->id == qosBucket) { selectedQueue = nqcb->oldQueues[i]; @@ -638,7 +631,7 @@ void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet & if (nqcb->inactiveQueues[i]->id == qosBucket) { selectedQueue = nqcb->inactiveQueues[i]; // move queue to end of NEW queue list - selectedQueue->byteCredit = ZT_QOS_QUANTUM; + selectedQueue->byteCredit = ZT_AQM_QUANTUM; // DEBUG_INFO("moving q=%p from INACTIVE to NEW list", selectedQueue); nqcb->newQueues.push_back(selectedQueue); nqcb->inactiveQueues.erase(nqcb->inactiveQueues.begin() + i); @@ -657,11 +650,11 @@ void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet & // Drop a packet if necessary ManagedQueue *selectedQueueToDropFrom = nullptr; - if (nqcb->_currEnqueuedPackets > ZT_QOS_MAX_ENQUEUED_PACKETS) + if (nqcb->_currEnqueuedPackets > ZT_AQM_MAX_ENQUEUED_PACKETS) { // DEBUG_INFO("too many enqueued packets (%d), finding packet to drop", nqcb->_currEnqueuedPackets); int maxQueueLength = 0; - for (size_t i=0; ioldQueues.size()) { if (nqcb->oldQueues[i]->byteLength > maxQueueLength) { maxQueueLength = nqcb->oldQueues[i]->byteLength; @@ -694,7 +687,7 @@ void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet & uint64_t Switch::control_law(uint64_t t, int count) { - return (uint64_t)(t + ZT_QOS_INTERVAL / sqrt(count)); + return (uint64_t)(t + ZT_AQM_INTERVAL / sqrt(count)); } Switch::dqr Switch::dodequeue(ManagedQueue *q, uint64_t now) @@ -708,14 +701,14 @@ Switch::dqr Switch::dodequeue(ManagedQueue *q, uint64_t now) return r; } uint64_t sojourn_time = now - r.p->creationTime; - if (sojourn_time < ZT_QOS_TARGET || q->byteLength <= ZT_DEFAULT_MTU) { + if (sojourn_time < ZT_AQM_TARGET || q->byteLength <= ZT_DEFAULT_MTU) { // went below - stay below for at least interval q->first_above_time = 0; } else { if (q->first_above_time == 0) { // just went above from below. if still above at // first_above_time, will say it's ok to drop. - q->first_above_time = now + ZT_QOS_INTERVAL; + q->first_above_time = now + ZT_AQM_INTERVAL; } else if (now >= q->first_above_time) { r.ok_to_drop = true; } @@ -747,7 +740,7 @@ Switch::TXQueueEntry * Switch::CoDelDequeue(ManagedQueue *q, bool isNew, uint64_ q->q.pop_front(); // drop r = dodequeue(q, now); q->dropping = true; - q->count = (q->count > 2 && now - q->drop_next < 8*ZT_QOS_INTERVAL)? + q->count = (q->count > 2 && now - q->drop_next < 8*ZT_AQM_INTERVAL)? q->count - 2 : 1; q->drop_next = control_law(now, q->count); } @@ -775,7 +768,7 @@ void Switch::aqm_dequeue(void *tPtr) while (currQueues->size()) { ManagedQueue *queueAtFrontOfList = currQueues->front(); if (queueAtFrontOfList->byteCredit < 0) { - queueAtFrontOfList->byteCredit += ZT_QOS_QUANTUM; + queueAtFrontOfList->byteCredit += ZT_AQM_QUANTUM; // Move to list of OLD queues // DEBUG_INFO("moving q=%p from NEW to OLD list", queueAtFrontOfList); oldQueues->push_back(queueAtFrontOfList); @@ -810,7 +803,7 @@ void Switch::aqm_dequeue(void *tPtr) while (currQueues->size()) { ManagedQueue *queueAtFrontOfList = currQueues->front(); if (queueAtFrontOfList->byteCredit < 0) { - queueAtFrontOfList->byteCredit += ZT_QOS_QUANTUM; + queueAtFrontOfList->byteCredit += ZT_AQM_QUANTUM; oldQueues->push_back(queueAtFrontOfList); currQueues->erase(currQueues->begin()); } else { @@ -850,7 +843,7 @@ void Switch::removeNetworkQoSControlBlock(uint64_t nwid) } } -void Switch::send(void *tPtr,Packet &packet,bool encrypt,int64_t flowId) +void Switch::send(void *tPtr,Packet &packet,bool encrypt,int32_t flowId) { const Address dest(packet.destination()); if (dest == RR->identity.address()) @@ -883,7 +876,7 @@ void Switch::requestWhois(void *tPtr,const int64_t now,const Address &addr) const SharedPtr upstream(RR->topology->getUpstreamPeer()); if (upstream) { - int64_t flowId = -1; + int32_t flowId = ZT_QOS_NO_FLOW; Packet outp(upstream->address(),RR->identity.address(),Packet::VERB_WHOIS); addr.appendTo(outp); RR->node->expectReplyTo(outp.packetId()); @@ -903,7 +896,7 @@ void Switch::doAnythingWaitingForPeer(void *tPtr,const SharedPtr &peer) RXQueueEntry *const rq = &(_rxQueue[ptr]); Mutex::Lock rql(rq->lock); if ((rq->timestamp)&&(rq->complete)) { - if ((rq->frag0.tryDecode(RR,tPtr))||((now - rq->timestamp) > ZT_RECEIVE_QUEUE_TIMEOUT)) + if ((rq->frag0.tryDecode(RR,tPtr,rq->flowId))||((now - rq->timestamp) > ZT_RECEIVE_QUEUE_TIMEOUT)) rq->timestamp = 0; } } @@ -954,7 +947,7 @@ unsigned long Switch::doTimerTasks(void *tPtr,int64_t now) RXQueueEntry *const rq = &(_rxQueue[ptr]); Mutex::Lock rql(rq->lock); if ((rq->timestamp)&&(rq->complete)) { - if ((rq->frag0.tryDecode(RR,tPtr))||((now - rq->timestamp) > ZT_RECEIVE_QUEUE_TIMEOUT)) { + if ((rq->frag0.tryDecode(RR,tPtr,rq->flowId))||((now - rq->timestamp) > ZT_RECEIVE_QUEUE_TIMEOUT)) { rq->timestamp = 0; } else { const Address src(rq->frag0.source()); @@ -1000,7 +993,7 @@ bool Switch::_shouldUnite(const int64_t now,const Address &source,const Address return false; } -bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt,int64_t flowId) +bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt,int32_t flowId) { SharedPtr viaPath; const int64_t now = RR->node->now(); @@ -1008,8 +1001,18 @@ bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt,int64_t flowId) const SharedPtr peer(RR->topology->getPeer(tPtr,destination)); if (peer) { - if (RR->node->getMultipathMode() == ZT_MULTIPATH_BROADCAST) { - // Nothing here, we'll grab an entire set of paths to send out on below + if ((peer->bondingPolicy() == ZT_BONDING_POLICY_BROADCAST) + && (packet.verb() == Packet::VERB_FRAME || packet.verb() == Packet::VERB_EXT_FRAME)) { + const SharedPtr relay(RR->topology->getUpstreamPeer()); + Mutex::Lock _l(peer->_paths_m); + for(int i=0;i_paths[i].p && peer->_paths[i].p->alive(now)) { + char pathStr[128]; + peer->_paths[i].p->address().toString(pathStr); + _sendViaSpecificPath(tPtr,peer,peer->_paths[i].p,now,packet,encrypt,flowId); + } + } + return true; } else { viaPath = peer->getAppropriatePath(now,false,flowId); @@ -1021,61 +1024,51 @@ bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt,int64_t flowId) return false; } } + if (viaPath) { + _sendViaSpecificPath(tPtr,peer,viaPath,now,packet,encrypt,flowId); + return true; + } } + } + return false; +} + +void Switch::_sendViaSpecificPath(void *tPtr,SharedPtr peer,SharedPtr viaPath,int64_t now,Packet &packet,bool encrypt,int32_t flowId) +{ + unsigned int mtu = ZT_DEFAULT_PHYSMTU; + uint64_t trustedPathId = 0; + RR->topology->getOutboundPathInfo(viaPath->address(),mtu,trustedPathId); + + unsigned int chunkSize = std::min(packet.size(),mtu); + packet.setFragmented(chunkSize < packet.size()); + + peer->recordOutgoingPacket(viaPath, packet.packetId(), packet.payloadLength(), packet.verb(), flowId, now); + + if (trustedPathId) { + packet.setTrusted(trustedPathId); } else { - return false; + packet.armor(peer->key(),encrypt); } - // If sending on all paths, set viaPath to first path - int nextPathIdx = 0; - std::vector> paths = peer->getAllPaths(now); - if (RR->node->getMultipathMode() == ZT_MULTIPATH_BROADCAST) { - if (paths.size()) { - viaPath = paths[nextPathIdx++]; - } - } + if (viaPath->send(RR,tPtr,packet.data(),chunkSize,now)) { + if (chunkSize < packet.size()) { + // Too big for one packet, fragment the rest + unsigned int fragStart = chunkSize; + unsigned int remaining = packet.size() - chunkSize; + unsigned int fragsRemaining = (remaining / (mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH)); + if ((fragsRemaining * (mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH)) < remaining) + ++fragsRemaining; + const unsigned int totalFragments = fragsRemaining + 1; - while (viaPath) { - unsigned int mtu = ZT_DEFAULT_PHYSMTU; - uint64_t trustedPathId = 0; - RR->topology->getOutboundPathInfo(viaPath->address(),mtu,trustedPathId); - unsigned int chunkSize = std::min(packet.size(),mtu); - packet.setFragmented(chunkSize < packet.size()); - peer->recordOutgoingPacket(viaPath, packet.packetId(), packet.payloadLength(), packet.verb(), now); - - if (trustedPathId) { - packet.setTrusted(trustedPathId); - } else { - packet.armor(peer->key(),encrypt); - } - - if (viaPath->send(RR,tPtr,packet.data(),chunkSize,now)) { - if (chunkSize < packet.size()) { - // Too big for one packet, fragment the rest - unsigned int fragStart = chunkSize; - unsigned int remaining = packet.size() - chunkSize; - unsigned int fragsRemaining = (remaining / (mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH)); - if ((fragsRemaining * (mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH)) < remaining) - ++fragsRemaining; - const unsigned int totalFragments = fragsRemaining + 1; - - for(unsigned int fno=1;fnosend(RR,tPtr,frag.data(),frag.size(),now); - fragStart += chunkSize; - remaining -= chunkSize; - } - } - } - viaPath.zero(); - if (RR->node->getMultipathMode() == ZT_MULTIPATH_BROADCAST) { - if (paths.size() > nextPathIdx) { - viaPath = paths[nextPathIdx++]; + for(unsigned int fno=1;fnosend(RR,tPtr,frag.data(),frag.size(),now); + fragStart += chunkSize; + remaining -= chunkSize; } } } - return true; } } // namespace ZeroTier diff --git a/node/Switch.hpp b/node/Switch.hpp index f535cb8eb..f1436c7cf 100644 --- a/node/Switch.hpp +++ b/node/Switch.hpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -59,6 +59,8 @@ class Switch struct ManagedQueue; struct TXQueueEntry; + friend class SharedPtr; + typedef struct { TXQueueEntry *p; bool ok_to_drop; @@ -123,7 +125,7 @@ public: * @param encrypt Encrypt packet payload? (always true except for HELLO) * @param qosBucket Which bucket the rule-system determined this packet should fall into */ - void aqm_enqueue(void *tPtr, const SharedPtr &network, Packet &packet,bool encrypt,int qosBucket,int64_t flowId = -1); + void aqm_enqueue(void *tPtr, const SharedPtr &network, Packet &packet,bool encrypt,int qosBucket,int32_t flowId = ZT_QOS_NO_FLOW); /** * Performs a single AQM cycle and dequeues and transmits all eligible packets on all networks @@ -169,7 +171,7 @@ public: * @param packet Packet to send (buffer may be modified) * @param encrypt Encrypt packet payload? (always true except for HELLO) */ - void send(void *tPtr,Packet &packet,bool encrypt,int64_t flowId = -1); + void send(void *tPtr,Packet &packet,bool encrypt,int32_t flowId = ZT_QOS_NO_FLOW); /** * Request WHOIS on a given address @@ -204,7 +206,8 @@ public: private: bool _shouldUnite(const int64_t now,const Address &source,const Address &destination); - bool _trySend(void *tPtr,Packet &packet,bool encrypt,int64_t flowId = -1); // packet is modified if return is true + bool _trySend(void *tPtr,Packet &packet,bool encrypt,int32_t flowId = ZT_QOS_NO_FLOW); // packet is modified if return is true + void _sendViaSpecificPath(void *tPtr,SharedPtr peer,SharedPtr viaPath,int64_t now,Packet &packet,bool encrypt,int32_t flowId); const RuntimeEnvironment *const RR; int64_t _lastBeaconResponse; @@ -225,6 +228,7 @@ private: unsigned int totalFragments; // 0 if only frag0 received, waiting for frags uint32_t haveFragments; // bit mask, LSB to MSB volatile bool complete; // if true, packet is complete + volatile int32_t flowId; Mutex lock; }; RXQueueEntry _rxQueue[ZT_RX_QUEUE_SIZE]; @@ -253,7 +257,7 @@ private: struct TXQueueEntry { TXQueueEntry() {} - TXQueueEntry(Address d,uint64_t ct,const Packet &p,bool enc,int64_t fid) : + TXQueueEntry(Address d,uint64_t ct,const Packet &p,bool enc,int32_t fid) : dest(d), creationTime(ct), packet(p), @@ -264,7 +268,7 @@ private: uint64_t creationTime; Packet packet; // unencrypted/unMAC'd packet -- this is done at send time bool encrypt; - int64_t flowId; + int32_t flowId; }; std::list< TXQueueEntry > _txQueue; Mutex _txQueue_m; @@ -296,7 +300,7 @@ private: { ManagedQueue(int id) : id(id), - byteCredit(ZT_QOS_QUANTUM), + byteCredit(ZT_AQM_QUANTUM), byteLength(0), dropping(false) {} diff --git a/node/Trace.cpp b/node/Trace.cpp index 96abf5c72..f7175c4c0 100644 --- a/node/Trace.cpp +++ b/node/Trace.cpp @@ -94,29 +94,26 @@ void Trace::peerConfirmingUnknownPath(void *const tPtr,const uint64_t networkId, } } -void Trace::peerLinkNowAggregate(void *const tPtr,Peer &peer) +void Trace::peerLinkNowRedundant(void *const tPtr,Peer &peer) { - if ((RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_RANDOM)) { - ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is now a randomly-distributed aggregate link",peer.address().toInt()); - } - if ((RR->node->getMultipathMode() == ZT_MULTIPATH_BALANCE_DYNAMIC_OPAQUE)) { - ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is now a proportionally-balanced aggregate link",peer.address().toInt()); - } + //ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is fully redundant",peer.address().toInt()); } -void Trace::peerLinkNoLongerAggregate(void *const tPtr,Peer &peer) +void Trace::peerLinkNoLongerRedundant(void *const tPtr,Peer &peer) { - ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx has degraded and is no longer an aggregate link",peer.address().toInt()); + //ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is no longer redundant",peer.address().toInt()); } void Trace::peerLinkAggregateStatistics(void *const tPtr,Peer &peer) { - ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is composed of (%d) physical paths %s, has PDV (%.0f ms), mean latency (%.0f ms)", + /* + ZT_LOCAL_TRACE(tPtr,RR,"link to peer %.10llx is composed of (%d) physical paths %s, has packet delay variance (%.0f ms), mean latency (%.0f ms)", peer.address().toInt(), peer.aggregateLinkPhysicalPathCount(), peer.interfaceListStr(), peer.computeAggregateLinkPacketDelayVariance(), peer.computeAggregateLinkMeanLatency()); + */ } void Trace::peerLearnedNewPath(void *const tPtr,const uint64_t networkId,Peer &peer,const SharedPtr &newPath,const uint64_t packetId) diff --git a/node/Trace.hpp b/node/Trace.hpp index b2a77161f..71169ebbb 100644 --- a/node/Trace.hpp +++ b/node/Trace.hpp @@ -109,8 +109,8 @@ public: void peerConfirmingUnknownPath(void *const tPtr,const uint64_t networkId,Peer &peer,const SharedPtr &path,const uint64_t packetId,const Packet::Verb verb); - void peerLinkNowAggregate(void *const tPtr,Peer &peer); - void peerLinkNoLongerAggregate(void *const tPtr,Peer &peer); + void peerLinkNowRedundant(void *const tPtr,Peer &peer); + void peerLinkNoLongerRedundant(void *const tPtr,Peer &peer); void peerLinkAggregateStatistics(void *const tPtr,Peer &peer); diff --git a/node/Utils.hpp b/node/Utils.hpp index 5ba5b035f..b80a7528d 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -214,12 +214,12 @@ public: return l; } - static inline float normalize(float value, int64_t bigMin, int64_t bigMax, int32_t targetMin, int32_t targetMax) + static inline float normalize(float value, float bigMin, float bigMax, float targetMin, float targetMax) { - int64_t bigSpan = bigMax - bigMin; - int64_t smallSpan = targetMax - targetMin; - float valueScaled = (value - (float)bigMin) / (float)bigSpan; - return (float)targetMin + valueScaled * (float)smallSpan; + float bigSpan = bigMax - bigMin; + float smallSpan = targetMax - targetMin; + float valueScaled = (value - bigMin) / bigSpan; + return targetMin + valueScaled * smallSpan; } /** @@ -253,6 +253,7 @@ public: static inline int strToInt(const char *s) { return (int)strtol(s,(char **)0,10); } static inline unsigned long strToULong(const char *s) { return strtoul(s,(char **)0,10); } static inline long strToLong(const char *s) { return strtol(s,(char **)0,10); } + static inline double strToDouble(const char *s) { return strtod(s,NULL); } static inline unsigned long long strToU64(const char *s) { #ifdef __WINDOWS__ diff --git a/objects.mk b/objects.mk index efa2f3c0f..b55ba3044 100644 --- a/objects.mk +++ b/objects.mk @@ -24,7 +24,9 @@ CORE_OBJS=\ node/Tag.o \ node/Topology.o \ node/Trace.o \ - node/Utils.o + node/Utils.o \ + node/Bond.o \ + node/BondController.o ONE_OBJS=\ controller/EmbeddedNetworkController.o \ diff --git a/osdep/Binder.hpp b/osdep/Binder.hpp index 660e6f0c3..0fde33452 100644 --- a/osdep/Binder.hpp +++ b/osdep/Binder.hpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -347,6 +347,23 @@ public: } } + // Generate set of unique interface names (used for formation of logical slave set in multipath code) + for(std::map::const_iterator ii(localIfAddrs.begin());ii!=localIfAddrs.end();++ii) { + slaveIfNames.insert(ii->second); + } + for (std::set::iterator si(slaveIfNames.begin());si!=slaveIfNames.end();si++) { + bool bFoundMatch = false; + for(std::map::const_iterator ii(localIfAddrs.begin());ii!=localIfAddrs.end();++ii) { + if (ii->second == *si) { + bFoundMatch = true; + break; + } + } + if (!bFoundMatch) { + slaveIfNames.erase(si); + } + } + // Create new bindings for those not already bound for(std::map::const_iterator ii(localIfAddrs.begin());ii!=localIfAddrs.end();++ii) { unsigned int bi = 0; @@ -444,7 +461,15 @@ public: return false; } + inline std::set getSlaveInterfaceNames() + { + Mutex::Lock _l(_lock); + return slaveIfNames; + } + private: + + std::set slaveIfNames; _Binding _bindings[ZT_BINDER_MAX_BINDINGS]; std::atomic _bindingCount; Mutex _lock; diff --git a/osdep/LinuxNetLink.cpp b/osdep/LinuxNetLink.cpp index 8d4ce2482..13e7176e4 100644 --- a/osdep/LinuxNetLink.cpp +++ b/osdep/LinuxNetLink.cpp @@ -55,8 +55,6 @@ LinuxNetLink::LinuxNetLink() { // set socket timeout to 1 sec so we're not permablocking recv() calls _setSocketTimeout(_fd, 1); - int yes=1; - setsockopt(_fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); _la.nl_family = AF_NETLINK; _la.nl_pid = 0; //getpid()+1; @@ -430,8 +428,6 @@ void LinuxNetLink::_linkDeleted(struct nlmsghdr *nlp) void LinuxNetLink::_requestIPv4Routes() { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - int yes=1; - setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; @@ -485,8 +481,6 @@ void LinuxNetLink::_requestIPv4Routes() void LinuxNetLink::_requestIPv6Routes() { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - int yes=1; - setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; @@ -540,8 +534,6 @@ void LinuxNetLink::_requestIPv6Routes() void LinuxNetLink::_requestInterfaceList() { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - int yes=1; - setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; @@ -595,8 +587,6 @@ void LinuxNetLink::addRoute(const InetAddress &target, const InetAddress &via, c if (!target) return; int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - int yes=1; - setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; @@ -713,8 +703,6 @@ void LinuxNetLink::delRoute(const InetAddress &target, const InetAddress &via, c if (!target) return; int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - int yes=1; - setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; @@ -828,8 +816,6 @@ void LinuxNetLink::delRoute(const InetAddress &target, const InetAddress &via, c void LinuxNetLink::addAddress(const InetAddress &addr, const char *iface) { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - int yes=1; - setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; @@ -948,8 +934,6 @@ void LinuxNetLink::addAddress(const InetAddress &addr, const char *iface) void LinuxNetLink::removeAddress(const InetAddress &addr, const char *iface) { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - int yes=1; - setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp index 3770f0217..537e14966 100644 --- a/osdep/OSUtils.cpp +++ b/osdep/OSUtils.cpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -459,6 +459,22 @@ uint64_t OSUtils::jsonInt(const nlohmann::json &jv,const uint64_t dfl) return dfl; } +double OSUtils::jsonDouble(const nlohmann::json &jv,const double dfl) +{ + try { + if (jv.is_number()) { + return (double)jv; + } + else if (jv.is_string()) { + std::string s = jv; + return Utils::strToDouble(s.c_str()); + } else if (jv.is_boolean()) { + return (double)jv; + } + } catch ( ... ) {} + return dfl; +} + uint64_t OSUtils::jsonIntHex(const nlohmann::json &jv,const uint64_t dfl) { try { diff --git a/osdep/OSUtils.hpp b/osdep/OSUtils.hpp index 172575a09..70a5daccc 100644 --- a/osdep/OSUtils.hpp +++ b/osdep/OSUtils.hpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -277,6 +277,7 @@ public: static nlohmann::json jsonParse(const std::string &buf); static std::string jsonDump(const nlohmann::json &j,int indentation = 1); static uint64_t jsonInt(const nlohmann::json &jv,const uint64_t dfl); + static double jsonDouble(const nlohmann::json &jv,const double dfl); static uint64_t jsonIntHex(const nlohmann::json &jv,const uint64_t dfl); static bool jsonBool(const nlohmann::json &jv,const bool dfl); static std::string jsonString(const nlohmann::json &jv,const char *dfl); diff --git a/osdep/Phy.hpp b/osdep/Phy.hpp index b65a520eb..30da8b395 100644 --- a/osdep/Phy.hpp +++ b/osdep/Phy.hpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -261,46 +261,6 @@ public: } } - /** - * Whether or not the socket object is in a closed state - * - * @param s Socket object - * @return true if socket is closed, false if otherwise - */ - inline bool isClosed(PhySocket *s) - { - PhySocketImpl *sws = (reinterpret_cast(s)); - return sws->type == ZT_PHY_SOCKET_CLOSED; - } - - /** - * Get state of socket object - * - * @param s Socket object - * @return State of socket - */ - inline int getState(PhySocket *s) - { - PhySocketImpl *sws = (reinterpret_cast(s)); - return sws->type; - } - - /** - * In the event that this socket is erased, we need a way to convey to the multipath logic - * that this path is no longer valid. - * - * @param s Socket object - * @return Whether the state of this socket is within an acceptable range of values - */ - inline bool isValidState(PhySocket *s) - { - if (s) { - PhySocketImpl *sws = (reinterpret_cast(s)); - return sws->type >= ZT_PHY_SOCKET_CLOSED && sws->type <= ZT_PHY_SOCKET_UNIX_LISTEN; - } - return false; - } - /** * Cause poll() to stop waiting immediately * diff --git a/osdep/Slave.hpp b/osdep/Slave.hpp new file mode 100644 index 000000000..b1ae326ea --- /dev/null +++ b/osdep/Slave.hpp @@ -0,0 +1,238 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2024-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +#ifndef ZT_SLAVE_HPP +#define ZT_SLAVE_HPP + +#include + +#include "../node/AtomicCounter.hpp" + +namespace ZeroTier { + +class Slave +{ + friend class SharedPtr; + +public: + + Slave() {} + + /** + * + * @param ifnameStr + * @param ipvPref + * @param speed + * @param enabled + * @param mode + * @param failoverToSlaveStr + * @param userSpecifiedAlloc + */ + Slave(std::string& ifnameStr, + uint8_t ipvPref, + uint32_t speed, + uint32_t slaveMonitorInterval, + uint32_t upDelay, + uint32_t downDelay, + bool enabled, + uint8_t mode, + std::string failoverToSlaveStr, + float userSpecifiedAlloc) : + _ifnameStr(ifnameStr), + _ipvPref(ipvPref), + _speed(speed), + _relativeSpeed(0), + _slaveMonitorInterval(slaveMonitorInterval), + _upDelay(upDelay), + _downDelay(downDelay), + _enabled(enabled), + _mode(mode), + _failoverToSlaveStr(failoverToSlaveStr), + _userSpecifiedAlloc(userSpecifiedAlloc), + _isUserSpecified(false) + {} + + /** + * @return The string representation of this slave's underlying interface's system name. + */ + inline std::string ifname() { return _ifnameStr; } + + /** + * @return Whether this slave is designated as a primary. + */ + inline bool primary() { return _mode == ZT_MULTIPATH_SLAVE_MODE_PRIMARY; } + + /** + * @return Whether this slave is designated as a spare. + */ + inline bool spare() { return _mode == ZT_MULTIPATH_SLAVE_MODE_SPARE; } + + /** + * @return The name of the slave interface that should be used in the event of a failure. + */ + inline std::string failoverToSlave() { return _failoverToSlaveStr; } + + /** + * @return Whether this slave interface was specified by the user or auto-detected. + */ + inline bool isUserSpecified() { return _isUserSpecified; } + + /** + * Signify that this slave was specified by the user and not the result of auto-detection. + * + * @param isUserSpecified + */ + inline void setAsUserSpecified(bool isUserSpecified) { _isUserSpecified = isUserSpecified; } + + /** + * @return Whether or not the user has specified failover instructions. + */ + inline bool userHasSpecifiedFailoverInstructions() { return _failoverToSlaveStr.length(); } + + /** + * @return The speed of the slave relative to others in the bond. + */ + inline uint8_t relativeSpeed() { return _relativeSpeed; } + + /** + * Sets the speed of the slave relative to others in the bond. + * + * @param relativeSpeed The speed relative to the rest of the slave interfaces. + */ + inline void setRelativeSpeed(uint8_t relativeSpeed) { _relativeSpeed = relativeSpeed; } + + /** + * Sets the speed of the slave relative to others in the bond. + * + * @param relativeSpeed + */ + inline void setMonitorInterval(uint32_t interval) { _slaveMonitorInterval = interval; } + + /** + * @return The absolute speed of the slave interface (as specified by the user.) + */ + inline uint32_t monitorInterval() { return _slaveMonitorInterval; } + + /** + * @return The absolute speed of the slave interface (as specified by the user.) + */ + inline uint32_t speed() { return _speed; } + + /** + * @return The address preference for this slave interface (as specified by the user.) + */ + inline uint8_t ipvPref() { return _ipvPref; } + + /** + * @return The mode (e.g. primary/spare) for this slave interface (as specified by the user.) + */ + inline uint8_t mode() { return _mode; } + + /** + * @return The upDelay parameter for all paths on this slave interface. + */ + inline uint32_t upDelay() { return _upDelay; } + + /** + * @return The downDelay parameter for all paths on this slave interface. + */ + inline uint32_t downDelay() { return _downDelay; } + + /** + * @return Whether this slave is enabled or disabled + */ + inline uint8_t enabled() { return _enabled; } + +private: + + /** + * String representation of underlying interface's system name + */ + std::string _ifnameStr; + + /** + * What preference (if any) a user has for IP protocol version used in + * path aggregations. Preference is expressed in the order of the digits: + * + * 0: no preference + * 4: IPv4 only + * 6: IPv6 only + * 46: IPv4 over IPv6 + * 64: IPv6 over IPv4 + */ + uint8_t _ipvPref; + + /** + * User-specified speed of this slave/link + */ + uint32_t _speed; + + /** + * Speed relative to other specified slaves/links (computed by Bond) + */ + uint8_t _relativeSpeed; + + /** + * User-specified interval for monitoring paths on this specific slave + * instead of using the more generic interval specified for the entire + * bond. + */ + uint32_t _slaveMonitorInterval; + + /** + * How long before a path is considered to be usable after coming online. (when using policies that + * support fail-over events). + */ + uint32_t _upDelay; + + /** + * How long before a path is considered to be dead (when using policies that + * support fail-over events). + */ + uint32_t _downDelay; + + /** + * Whether this slave is enabled, or (disabled (possibly bad config)) + */ + uint8_t _enabled; + + /** + * Whether this slave is designated as a primary, a spare, or no preference. + */ + uint8_t _mode; + + /** + * The specific name of the interface to be used in the event that this + * slave fails. + */ + std::string _failoverToSlaveStr; + + /** + * User-specified allocation + */ + float _userSpecifiedAlloc; + + /** + * Whether or not this slave was created as a result of manual user specification. This is + * important to know because certain policy decisions are dependent on whether the user + * intents to use a specific set of interfaces. + */ + bool _isUserSpecified; + + AtomicCounter __refCount; + +}; + +} // namespace ZeroTier + +#endif \ No newline at end of file diff --git a/service/OneService.cpp b/service/OneService.cpp index 22c4f82e9..2b1cb631f 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -1,10 +1,10 @@ /* - * Copyright (c)2019 ZeroTier, Inc. + * Copyright (c)2013-2020 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * - * Change Date: 2023-01-01 + * Change Date: 2024-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. @@ -39,6 +39,8 @@ #include "../node/Salsa20.hpp" #include "../node/Poly1305.hpp" #include "../node/SHA512.hpp" +#include "../node/Bond.hpp" +#include "../node/Peer.hpp" #include "../osdep/Phy.hpp" #include "../osdep/Thread.hpp" @@ -48,6 +50,7 @@ #include "../osdep/Binder.hpp" #include "../osdep/ManagedRoute.hpp" #include "../osdep/BlockingQueue.hpp" +#include "../osdep/Slave.hpp" #include "OneService.hpp" #include "SoftwareUpdater.hpp" @@ -266,37 +269,43 @@ static void _peerToJson(nlohmann::json &pj,const ZT_Peer *peer) pj["paths"] = pa; } -static void _peerAggregateLinkToJson(nlohmann::json &pj,const ZT_Peer *peer) +static void _peerBondToJson(nlohmann::json &pj,const ZT_Peer *peer) { char tmp[256]; OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.10llx",peer->address); - pj["aggregateLinkLatency"] = peer->latency; + //pj["aggregateLinkLatency"] = peer->latency; + std::string policyStr = BondController::getPolicyStrByCode(peer->bondingPolicy); + pj["policy"] = policyStr; nlohmann::json pa = nlohmann::json::array(); for(unsigned int i=0;ipathCount;++i) { int64_t lastSend = peer->paths[i].lastSend; int64_t lastReceive = peer->paths[i].lastReceive; nlohmann::json j; - j["address"] = reinterpret_cast(&(peer->paths[i].address))->toString(tmp); - j["lastSend"] = (lastSend < 0) ? 0 : lastSend; - j["lastReceive"] = (lastReceive < 0) ? 0 : lastReceive; + j["ifname"] = std::string(peer->paths[i].ifname); + j["path"] = reinterpret_cast(&(peer->paths[i].address))->toString(tmp); + j["lastTX"] = (lastSend < 0) ? 0 : lastSend; + j["lastRX"] = (lastReceive < 0) ? 0 : lastReceive; + j["lat"] = peer->paths[i].latencyMean; + j["pdv"] = peer->paths[i].latencyVariance; + //j["trustedPathId"] = peer->paths[i].trustedPathId; //j["active"] = (bool)(peer->paths[i].expired == 0); //j["expired"] = (bool)(peer->paths[i].expired != 0); //j["preferred"] = (bool)(peer->paths[i].preferred != 0); - j["latency"] = peer->paths[i].latency; - j["pdv"] = peer->paths[i].packetDelayVariance; - //j["throughputDisturbCoeff"] = peer->paths[i].throughputDisturbCoeff; - //j["packetErrorRatio"] = peer->paths[i].packetErrorRatio; - //j["packetLossRatio"] = peer->paths[i].packetLossRatio; - j["stability"] = peer->paths[i].stability; - j["throughput"] = peer->paths[i].throughput; - //j["maxThroughput"] = peer->paths[i].maxThroughput; - j["allocation"] = peer->paths[i].allocation; - j["ifname"] = peer->paths[i].ifname; + //j["ltm"] = peer->paths[i].latencyMax; + //j["plr"] = peer->paths[i].packetLossRatio; + //j["per"] = peer->paths[i].packetErrorRatio; + //j["thr"] = peer->paths[i].throughputMean; + //j["thm"] = peer->paths[i].throughputMax; + //j["thv"] = peer->paths[i].throughputVariance; + //j["avl"] = peer->paths[i].availability; + //j["age"] = peer->paths[i].age; + //j["alloc"] = peer->paths[i].allocation; + //j["ifname"] = peer->paths[i].ifname; pa.push_back(j); } - pj["paths"] = pa; + pj["slaves"] = pa; } static void _moonToJson(nlohmann::json &mj,const World &world) @@ -429,7 +438,7 @@ public: bool _updateAutoApply; bool _allowTcpFallbackRelay; bool _allowSecondaryPort; - unsigned int _multipathMode; + unsigned int _primaryPort; unsigned int _secondaryPort; unsigned int _tertiaryPort; @@ -718,6 +727,7 @@ public: } } #endif + // Delete legacy iddb.d if present (cleanup) OSUtils::rmDashRf((_homePath + ZT_PATH_SEPARATOR_S "iddb.d").c_str()); @@ -752,7 +762,6 @@ public: int64_t lastTapMulticastGroupCheck = 0; int64_t lastBindRefresh = 0; int64_t lastUpdateCheck = clockShouldBe; - int64_t lastMultipathModeUpdate = 0; int64_t lastCleanedPeersDb = 0; int64_t lastLocalInterfaceAddressCheck = (clockShouldBe - ZT_LOCAL_INTERFACE_CHECK_INTERVAL) + 15000; // do this in 15s to give portmapper time to configure and other things time to settle int64_t lastLocalConfFileCheck = OSUtils::now(); @@ -798,7 +807,7 @@ public: } // Refresh bindings in case device's interfaces have changed, and also sync routes to update any shadow routes (e.g. shadow default) - if (((now - lastBindRefresh) >= (_multipathMode ? ZT_BINDER_REFRESH_PERIOD / 8 : ZT_BINDER_REFRESH_PERIOD))||(restarted)) { + if (((now - lastBindRefresh) >= (_node->bondController()->inUse() ? ZT_BINDER_REFRESH_PERIOD / 4 : ZT_BINDER_REFRESH_PERIOD))||(restarted)) { lastBindRefresh = now; unsigned int p[3]; unsigned int pc = 0; @@ -815,11 +824,6 @@ public: } } } - // Update multipath mode (if needed) - if (((now - lastMultipathModeUpdate) >= ZT_BINDER_REFRESH_PERIOD / 8)||(restarted)) { - lastMultipathModeUpdate = now; - _node->setMultipathMode(_multipathMode); - } // Run background task processor in core if it's time to do so int64_t dl = _nextBackgroundTaskDeadline; @@ -855,7 +859,7 @@ public: } // Sync information about physical network interfaces - if ((now - lastLocalInterfaceAddressCheck) >= (_multipathMode ? ZT_LOCAL_INTERFACE_CHECK_INTERVAL / 8 : ZT_LOCAL_INTERFACE_CHECK_INTERVAL)) { + if ((now - lastLocalInterfaceAddressCheck) >= (_node->bondController()->inUse() ? ZT_LOCAL_INTERFACE_CHECK_INTERVAL / 8 : ZT_LOCAL_INTERFACE_CHECK_INTERVAL)) { lastLocalInterfaceAddressCheck = now; _node->clearLocalInterfaceAddresses(); @@ -869,8 +873,9 @@ public: #endif std::vector boundAddrs(_binder.allBoundLocalInterfaceAddresses()); - for(std::vector::const_iterator i(boundAddrs.begin());i!=boundAddrs.end();++i) + for(std::vector::const_iterator i(boundAddrs.begin());i!=boundAddrs.end();++i) { _node->addLocalInterfaceAddress(reinterpret_cast(&(*i))); + } } // Clean peers.d periodically @@ -1209,15 +1214,15 @@ public: settings["primaryPort"] = OSUtils::jsonInt(settings["primaryPort"],(uint64_t)_primaryPort) & 0xffff; settings["allowTcpFallbackRelay"] = OSUtils::jsonBool(settings["allowTcpFallbackRelay"],_allowTcpFallbackRelay); - if (_multipathMode) { - json &multipathConfig = res["multipath"]; + if (_node->bondController()->inUse()) { + json &multipathConfig = res["bonds"]; ZT_PeerList *pl = _node->peers(); char peerAddrStr[256]; if (pl) { for(unsigned long i=0;ipeerCount;++i) { - if (pl->peers[i].hadAggregateLink) { + if (pl->peers[i].isBonded) { nlohmann::json pj; - _peerAggregateLinkToJson(pj,&(pl->peers[i])); + _peerBondToJson(pj,&(pl->peers[i])); OSUtils::ztsnprintf(peerAddrStr,sizeof(peerAddrStr),"%.10llx",pl->peers[i].address); multipathConfig[peerAddrStr] = (pj); } @@ -1346,8 +1351,8 @@ public: if (j.is_object()) { seed = Utils::hexStrToU64(OSUtils::jsonString(j["seed"],"0").c_str()); } - } catch (std::exception &exc) { } catch ( ... ) { + // discard invalid JSON } std::vector moons(_node->moons()); @@ -1396,8 +1401,8 @@ public: json &allowDefault = j["allowDefault"]; if (allowDefault.is_boolean()) localSettings.allowDefault = (bool)allowDefault; } - } catch (std::exception &exc) { } catch ( ... ) { + // discard invalid JSON } setNetworkSettings(nws->networks[i].nwid,localSettings); @@ -1551,10 +1556,133 @@ public: json &settings = lc["settings"]; + if (!_node->bondController()->inUse()) { + // defaultBondingPolicy + std::string defaultBondingPolicyStr(OSUtils::jsonString(settings["defaultBondingPolicy"],"")); + int defaultBondingPolicy = _node->bondController()->getPolicyCodeByStr(defaultBondingPolicyStr); + _node->bondController()->setBondingLayerDefaultPolicy(defaultBondingPolicy); + _node->bondController()->setBondingLayerDefaultPolicyStr(defaultBondingPolicyStr); // Used if custom policy + // Custom Policies + json &customBondingPolicies = settings["policies"]; + for (json::iterator policyItr = customBondingPolicies.begin(); policyItr != customBondingPolicies.end();++policyItr) { + fprintf(stderr, "\n\n--- (%s)\n", policyItr.key().c_str()); + // Custom Policy + std::string customPolicyStr(policyItr.key()); + json &customPolicy = policyItr.value(); + std::string basePolicyStr(OSUtils::jsonString(customPolicy["basePolicy"],"")); + if (_node->bondController()->getPolicyCodeByStr(basePolicyStr) == ZT_BONDING_POLICY_NONE) { + fprintf(stderr, "error: custom policy (%s) is invalid, unknown base policy (%s).\n", + customPolicyStr.c_str(), basePolicyStr.c_str()); + continue; + } if (_node->bondController()->getPolicyCodeByStr(customPolicyStr) != ZT_BONDING_POLICY_NONE) { + fprintf(stderr, "error: custom policy (%s) will be ignored, cannot use standard policy names for custom policies.\n", + customPolicyStr.c_str()); + continue; + } + // New bond, used as a copy template for new instances + SharedPtr newTemplateBond = new Bond(basePolicyStr, customPolicyStr, SharedPtr()); + // Acceptable ranges + newTemplateBond->setMaxAcceptableLatency(OSUtils::jsonInt(customPolicy["maxAcceptableLatency"],-1)); + newTemplateBond->setMaxAcceptableMeanLatency(OSUtils::jsonInt(customPolicy["maxAcceptableMeanLatency"],-1)); + newTemplateBond->setMaxAcceptablePacketDelayVariance(OSUtils::jsonInt(customPolicy["maxAcceptablePacketDelayVariance"],-1)); + newTemplateBond->setMaxAcceptablePacketLossRatio((float)OSUtils::jsonDouble(customPolicy["maxAcceptablePacketLossRatio"],-1)); + newTemplateBond->setMaxAcceptablePacketErrorRatio((float)OSUtils::jsonDouble(customPolicy["maxAcceptablePacketErrorRatio"],-1)); + newTemplateBond->setMinAcceptableAllocation((float)OSUtils::jsonDouble(customPolicy["minAcceptableAllocation"],0)); + // Quality weights + json &qualityWeights = customPolicy["qualityWeights"]; + if (qualityWeights.size() == ZT_QOS_WEIGHT_SIZE) { // TODO: Generalize this + float weights[ZT_QOS_WEIGHT_SIZE]; + weights[ZT_QOS_LAT_IDX] = (float)OSUtils::jsonDouble(qualityWeights["lat"],0.0); + weights[ZT_QOS_LTM_IDX] = (float)OSUtils::jsonDouble(qualityWeights["ltm"],0.0); + weights[ZT_QOS_PDV_IDX] = (float)OSUtils::jsonDouble(qualityWeights["pdv"],0.0); + weights[ZT_QOS_PLR_IDX] = (float)OSUtils::jsonDouble(qualityWeights["plr"],0.0); + weights[ZT_QOS_PER_IDX] = (float)OSUtils::jsonDouble(qualityWeights["per"],0.0); + weights[ZT_QOS_THR_IDX] = (float)OSUtils::jsonDouble(qualityWeights["thr"],0.0); + weights[ZT_QOS_THM_IDX] = (float)OSUtils::jsonDouble(qualityWeights["thm"],0.0); + weights[ZT_QOS_THV_IDX] = (float)OSUtils::jsonDouble(qualityWeights["thv"],0.0); + newTemplateBond->setUserQualityWeights(weights,ZT_QOS_WEIGHT_SIZE); + } + // Bond-specific properties + newTemplateBond->setUpDelay(OSUtils::jsonInt(customPolicy["upDelay"],-1)); + newTemplateBond->setDownDelay(OSUtils::jsonInt(customPolicy["downDelay"],-1)); + newTemplateBond->setFailoverInterval(OSUtils::jsonInt(customPolicy["failoverInterval"],(uint64_t)0)); + newTemplateBond->setPacketsPerSlave(OSUtils::jsonInt(customPolicy["packetsPerSlave"],-1)); + std::string slaveMonitorStrategyStr(OSUtils::jsonString(customPolicy["slaveMonitorStrategy"],"")); + uint8_t slaveMonitorStrategy = ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DEFAULT; + if (slaveMonitorStrategyStr == "passive") { newTemplateBond->setSlaveMonitorStrategy(ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_PASSIVE); } + if (slaveMonitorStrategyStr == "active") { newTemplateBond->setSlaveMonitorStrategy(ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_ACTIVE); } + if (slaveMonitorStrategyStr == "dynamic") { newTemplateBond->setSlaveMonitorStrategy(ZT_MULTIPATH_SLAVE_MONITOR_STRATEGY_DYNAMIC); } + // Policy-Specific slave set + json &slaves = customPolicy["slaves"]; + for (json::iterator slaveItr = slaves.begin(); slaveItr != slaves.end();++slaveItr) { + fprintf(stderr, "\t--- slave (%s)\n", slaveItr.key().c_str()); + std::string slaveNameStr(slaveItr.key()); + json &slave = slaveItr.value(); + + bool enabled = OSUtils::jsonInt(slave["enabled"],true); + uint32_t speed = OSUtils::jsonInt(slave["speed"],0); + float alloc = (float)OSUtils::jsonDouble(slave["alloc"],0); + + if (speed && alloc) { + fprintf(stderr, "error: cannot specify both speed (%d) and alloc (%f) for slave (%s), pick one, slave disabled.\n", + speed, alloc, slaveNameStr.c_str()); + enabled = false; + } + uint32_t upDelay = OSUtils::jsonInt(slave["upDelay"],-1); + uint32_t downDelay = OSUtils::jsonInt(slave["downDelay"],-1); + uint8_t ipvPref = OSUtils::jsonInt(slave["ipvPref"],0); + uint32_t slaveMonitorInterval = OSUtils::jsonInt(slave["monitorInterval"],(uint64_t)0); + std::string failoverToStr(OSUtils::jsonString(slave["failoverTo"],"")); + // Mode + std::string slaveModeStr(OSUtils::jsonString(slave["mode"],"spare")); + uint8_t slaveMode = ZT_MULTIPATH_SLAVE_MODE_SPARE; + if (slaveModeStr == "primary") { slaveMode = ZT_MULTIPATH_SLAVE_MODE_PRIMARY; } + if (slaveModeStr == "spare") { slaveMode = ZT_MULTIPATH_SLAVE_MODE_SPARE; } + // ipvPref + if ((ipvPref != 0) && (ipvPref != 4) && (ipvPref != 6) && (ipvPref != 46) && (ipvPref != 64)) { + fprintf(stderr, "error: invalid ipvPref value (%d), slave disabled.\n", ipvPref); + enabled = false; + } + if (slaveMode == ZT_MULTIPATH_SLAVE_MODE_SPARE && failoverToStr.length()) { + fprintf(stderr, "error: cannot specify failover slaves for spares, slave disabled.\n"); + failoverToStr = ""; + enabled = false; + } + _node->bondController()->addCustomSlave(customPolicyStr, new Slave(slaveNameStr,ipvPref,speed,slaveMonitorInterval,upDelay,downDelay,enabled,slaveMode,failoverToStr,alloc)); + } + // TODO: This is dumb + std::string slaveSelectMethodStr(OSUtils::jsonString(customPolicy["activeReselect"],"optimize")); + if (slaveSelectMethodStr == "always") { newTemplateBond->setSlaveSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_ALWAYS); } + if (slaveSelectMethodStr == "better") { newTemplateBond->setSlaveSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_BETTER); } + if (slaveSelectMethodStr == "failure") { newTemplateBond->setSlaveSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_FAILURE); } + if (slaveSelectMethodStr == "optimize") { newTemplateBond->setSlaveSelectMethod(ZT_MULTIPATH_RESELECTION_POLICY_OPTIMIZE); } + if (newTemplateBond->getSlaveSelectMethod() < 0 || newTemplateBond->getSlaveSelectMethod() > 3) { + fprintf(stderr, "warning: invalid value (%s) for slaveSelectMethod, assuming mode: always\n", slaveSelectMethodStr.c_str()); + } + /* + newBond->setPolicy(_node->bondController()->getPolicyCodeByStr(basePolicyStr)); + newBond->setFlowHashing((bool)OSUtils::jsonInt(userSpecifiedBondingPolicies[i]["allowFlowHashing"],(bool)allowFlowHashing)); + newBond->setBondMonitorInterval((unsigned int)OSUtils::jsonInt(userSpecifiedBondingPolicies[i]["monitorInterval"],(uint64_t)0)); + newBond->setAllowPathNegotiation((bool)OSUtils::jsonInt(userSpecifiedBondingPolicies[i]["allowPathNegotiation"],(bool)false)); + */ + if (!_node->bondController()->addCustomPolicy(newTemplateBond)) { + fprintf(stderr, "error: a custom policy of this name (%s) already exists.\n", customPolicyStr.c_str()); + } + } + // Peer-specific bonding + json &peerSpecificBonds = settings["peerSpecificBonds"]; + for (json::iterator peerItr = peerSpecificBonds.begin(); peerItr != peerSpecificBonds.end();++peerItr) { + _node->bondController()->assignBondingPolicyToPeer(std::stoull(peerItr.key(),0,16), peerItr.value()); + } + // Check settings + if (defaultBondingPolicyStr.length() && !defaultBondingPolicy && !_node->bondController()->inUse()) { + fprintf(stderr, "error: unknown policy (%s) specified by defaultBondingPolicy, slave disabled.\n", defaultBondingPolicyStr.c_str()); + } + } + + // bondingPolicy cannot be used with allowTcpFallbackRelay + _allowTcpFallbackRelay = OSUtils::jsonBool(settings["allowTcpFallbackRelay"],true) && !(_node->bondController()->inUse()); _primaryPort = (unsigned int)OSUtils::jsonInt(settings["primaryPort"],(uint64_t)_primaryPort) & 0xffff; - _multipathMode = (unsigned int)OSUtils::jsonInt(settings["multipathMode"],0); - // multipathMode cannot be used with allowTcpFallbackRelay - _allowTcpFallbackRelay = OSUtils::jsonBool(settings["allowTcpFallbackRelay"],true) && !_multipathMode; _allowSecondaryPort = OSUtils::jsonBool(settings["allowSecondaryPort"],true); _secondaryPort = (unsigned int)OSUtils::jsonInt(settings["secondaryPort"],0); _tertiaryPort = (unsigned int)OSUtils::jsonInt(settings["tertiaryPort"],0); @@ -1705,9 +1833,8 @@ public: } } #ifdef __SYNOLOGY__ - if (!n.tap->addIps(newManagedIps)) { + if (!n.tap->addIpSyn(newManagedIps)) fprintf(stderr,"ERROR: unable to add ip addresses to ifcfg" ZT_EOL_S); - } #else for(std::vector::iterator ip(newManagedIps.begin());ip!=newManagedIps.end();++ip) { if (std::find(n.managedIps.begin(),n.managedIps.end(),*ip) == n.managedIps.end()) { @@ -2025,8 +2152,6 @@ public: return; } - } catch (std::exception &exc) { - _phy.close(sock); } catch ( ... ) { _phy.close(sock); } @@ -2135,8 +2260,6 @@ public: #endif _nets.erase(nwid); return -999; - } catch (int exc) { - return -999; } catch ( ... ) { return -999; // tap init failed } @@ -2743,6 +2866,7 @@ public: if (!strncmp(p->c_str(),ifname,p->length())) return false; } + return _node->bondController()->allowedToBind(std::string(ifname)); } { // Check global blacklists From 563655a1a410f6b86d86b3f3747bc1a8d2e88d76 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 12 May 2020 11:56:19 -0700 Subject: [PATCH 053/362] Redis now usable as a message queue --- controller/PostgreSQL.cpp | 107 +++++++++++++++++++++++++++++++++----- controller/PostgreSQL.hpp | 7 +-- make-mac.mk | 3 +- 3 files changed, 101 insertions(+), 16 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 0640cb8ee..31d5b9118 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -68,6 +68,10 @@ std::string join(const std::vector &elements, const char * const se using namespace ZeroTier; +using Attrs = std::vector>; +using Item = std::pair; +using ItemStream = std::vector; + PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, RedisConfig *rc) : DB() , _myId(myId) @@ -124,9 +128,9 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, R opts.db = 0; poolOpts.size = 10; if (_rc->clusterMode) { - _cluster = new sw::redis::RedisCluster(opts, poolOpts); + _cluster = std::make_shared(opts, poolOpts); } else { - _redis = new sw::redis::Redis(opts, poolOpts); + _redis = std::make_shared(opts, poolOpts); } } @@ -145,17 +149,15 @@ PostgreSQL::~PostgreSQL() _run = 0; std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - _heartbeatThread.join(); _membersDbWatcher.join(); _networksDbWatcher.join(); + _commitQueue.stop(); for (int i = 0; i < ZT_CENTRAL_CONTROLLER_COMMIT_THREADS; ++i) { _commitThread[i].join(); } _onlineNotificationThread.join(); - delete _redis; - delete _cluster; + fprintf(stderr, "~PostgreSQL() done\n"); } @@ -651,6 +653,7 @@ void PostgreSQL::heartbeat() PQfinish(conn); conn = NULL; + fprintf(stderr, "Exited heartbeat thread\n"); } void PostgreSQL::membersDbWatcher() @@ -664,10 +667,10 @@ void PostgreSQL::membersDbWatcher() initializeMembers(conn); - if (false) { - // PQfinish(conn); - // conn = NULL; - // _membersWatcher_RabbitMQ(); + if (_rc) { + PQfinish(conn); + conn = NULL; + _membersWatcher_Redis(); } else { _membersWatcher_Postgres(conn); PQfinish(conn); @@ -722,9 +725,47 @@ void PostgreSQL::_membersWatcher_Postgres(PGconn *conn) { } } -void PostgreSQL::_membersWatcher_Reids() { - char buff[11] = {0}; +void PostgreSQL::_membersWatcher_Redis() { + char buf[11] = {0}; + std::string key = "member-stream:{" + std::string(_myAddress.toString(buf)) + "}"; + while (_run == 1) { + json tmp; + std::unordered_map result; + if (_rc->clusterMode) { + _cluster->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); + } else { + _redis->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); + } + if (!result.empty()) { + for (auto element : result) { + fprintf(stdout, "Received notification from: %s\n", element.first.c_str()); + for (auto rec : element.second) { + std::string id = rec.first; + auto attrs = rec.second; + fprintf(stdout, "Record ID: %s\n", id.c_str()); + fprintf(stdout, "attrs len: %lu\n", attrs.size()); + for (auto a : attrs) { + fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str()); + try { + tmp = json::parse(a.second); + json &ov = tmp["old_val"]; + json &nv = tmp["new_val"]; + json oldConfig, newConfig; + if (ov.is_object()) oldConfig = ov; + if (nv.is_object()) newConfig = nv; + if (oldConfig.is_object()||newConfig.is_object()) { + _memberChanged(oldConfig,newConfig,(this->_ready >= 2)); + } + } catch (...) { + fprintf(stderr, "json parse error in networkWatcher_Redis\n"); + } + } + } + } + } + } + fprintf(stderr, "membersWatcher ended\n"); } void PostgreSQL::networksDbWatcher() @@ -795,7 +836,48 @@ void PostgreSQL::_networksWatcher_Postgres(PGconn *conn) { } void PostgreSQL::_networksWatcher_Redis() { + char buf[11] = {0}; + std::string key = "network-stream:{" + std::string(_myAddress.toString(buf)) + "}"; + + while (_run == 1) { + json tmp; + std::unordered_map result; + if (_rc->clusterMode) { + _cluster->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); + } else { + _redis->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); + } + + if (!result.empty()) { + for (auto element : result) { + fprintf(stdout, "Received notification from: %s\n", element.first.c_str()); + for (auto rec : element.second) { + std::string id = rec.first; + auto attrs = rec.second; + fprintf(stdout, "Record ID: %s\n", id.c_str()); + fprintf(stdout, "attrs len: %lu\n", attrs.size()); + for (auto a : attrs) { + fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str()); + try { + tmp = json::parse(a.second); + json &ov = tmp["old_val"]; + json &nv = tmp["new_val"]; + json oldConfig, newConfig; + if (ov.is_object()) oldConfig = ov; + if (nv.is_object()) newConfig = nv; + if (oldConfig.is_object()||newConfig.is_object()) { + _networkChanged(oldConfig,newConfig,(this->_ready >= 2)); + } + } catch (...) { + fprintf(stderr, "json parse error in networkWatcher_Redis\n"); + } + } + } + } + } + } + fprintf(stderr, "networksWatcher ended\n"); } void PostgreSQL::commitThread() @@ -1293,6 +1375,7 @@ void PostgreSQL::commitThread() fprintf(stderr, "ERROR: %s commitThread should still be running! Exiting Controller.\n", _myAddressStr.c_str()); exit(7); } + fprintf(stderr, "commitThread finished\n"); } void PostgreSQL::onlineNotificationThread() diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index 986559acf..44347cd81 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -20,6 +20,7 @@ #define ZT_CENTRAL_CONTROLLER_COMMIT_THREADS 4 +#include #include extern "C" { @@ -64,7 +65,7 @@ private: void networksDbWatcher(); void _networksWatcher_Postgres(PGconn *conn); - void _membersWatcher_Reids(); + void _membersWatcher_Redis(); void _networksWatcher_Redis(); void commitThread(); @@ -100,8 +101,8 @@ private: int _listenPort; RedisConfig *_rc; - sw::redis::Redis *_redis; - sw::redis::RedisCluster *_cluster; + std::shared_ptr _redis; + std::shared_ptr _cluster; }; } // namespace ZeroTier diff --git a/make-mac.mk b/make-mac.mk index 6625dc85e..5e7a67c20 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -28,9 +28,10 @@ include objects.mk ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o ext/http-parser/http_parser.o ifeq ($(ZT_CONTROLLER),1) - LIBS+=-L/usr/local/opt/libpq/lib -lpq -Lext/redis-plus-plus-1.1.1/install/macos/lib -lredis++ -Lext/hiredis-0.14.1/lib/macos -lhiredis + LIBS+=-L/usr/local/opt/libpq/lib -lpq ext/redis-plus-plus-1.1.1/install/macos/lib/libredis++.a ext/hiredis-0.14.1/lib/macos/libhiredis.a DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER_USE_REDIS -DZT_CONTROLLER INCLUDES+=-I/usr/local/opt/libpq/include -Iext/hiredis-0.14.1/include/ -Iext/redis-plus-plus-1.1.1/install/macos/include/sw/ + endif # Official releases are signed with our Apple cert and apply software updates by default From c6518afa7a611a59b00fe941cd53055066a02e41 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 12 May 2020 12:37:05 -0700 Subject: [PATCH 054/362] Make sure the streams clean up after themselves --- controller/PostgreSQL.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 31d5b9118..e3d513a06 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -761,6 +761,11 @@ void PostgreSQL::_membersWatcher_Redis() { fprintf(stderr, "json parse error in networkWatcher_Redis\n"); } } + if (_rc->clusterMode) { + _cluster->xdel(key, id); + } else { + _redis->xdel(key, id); + } } } } @@ -873,6 +878,11 @@ void PostgreSQL::_networksWatcher_Redis() { fprintf(stderr, "json parse error in networkWatcher_Redis\n"); } } + if (_rc->clusterMode) { + _cluster->xdel(key, id); + } else { + _redis->xdel(key, id); + } } } } From aab96964b6ce93f452d0e170e969dd50a2ea2540 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 12 May 2020 12:48:58 -0700 Subject: [PATCH 055/362] Put debug output behind ZT_TRACE --- controller/PostgreSQL.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index e3d513a06..505d76527 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -157,7 +157,6 @@ PostgreSQL::~PostgreSQL() _commitThread[i].join(); } _onlineNotificationThread.join(); - fprintf(stderr, "~PostgreSQL() done\n"); } @@ -739,14 +738,20 @@ void PostgreSQL::_membersWatcher_Redis() { } if (!result.empty()) { for (auto element : result) { +#ifdef ZT_TRACE fprintf(stdout, "Received notification from: %s\n", element.first.c_str()); +#endif for (auto rec : element.second) { std::string id = rec.first; auto attrs = rec.second; +#ifdef ZT_TRACE fprintf(stdout, "Record ID: %s\n", id.c_str()); fprintf(stdout, "attrs len: %lu\n", attrs.size()); +#endif for (auto a : attrs) { +#ifdef ZT_TRACE fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str()); +#endif try { tmp = json::parse(a.second); json &ov = tmp["old_val"]; @@ -855,15 +860,20 @@ void PostgreSQL::_networksWatcher_Redis() { if (!result.empty()) { for (auto element : result) { - +#ifdef ZT_TRACE fprintf(stdout, "Received notification from: %s\n", element.first.c_str()); +#endif for (auto rec : element.second) { std::string id = rec.first; auto attrs = rec.second; +#ifdef ZT_TRACE fprintf(stdout, "Record ID: %s\n", id.c_str()); fprintf(stdout, "attrs len: %lu\n", attrs.size()); +#endif for (auto a : attrs) { +#ifdef ZT_TRACE fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str()); +#endif try { tmp = json::parse(a.second); json &ov = tmp["old_val"]; From 5babd01d407b45d70d31291fec18442d78fe7717 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 12 May 2020 12:58:09 -0700 Subject: [PATCH 056/362] centos8 binaries for libhiredis and libredis++ --- ext/hiredis-0.14.1/lib/centos8/libhiredis.a | Bin 0 -> 485314 bytes .../centos8/include/sw/redis++/command.h | 2233 +++++++++++++++++ .../centos8/include/sw/redis++/command_args.h | 180 ++ .../include/sw/redis++/command_options.h | 211 ++ .../centos8/include/sw/redis++/connection.h | 194 ++ .../include/sw/redis++/connection_pool.h | 115 + .../centos8/include/sw/redis++/errors.h | 159 ++ .../centos8/include/sw/redis++/pipeline.h | 49 + .../centos8/include/sw/redis++/queued_redis.h | 1844 ++++++++++++++ .../include/sw/redis++/queued_redis.hpp | 208 ++ .../centos8/include/sw/redis++/redis++.h | 25 + .../centos8/include/sw/redis++/redis.h | 1523 +++++++++++ .../centos8/include/sw/redis++/redis.hpp | 1365 ++++++++++ .../include/sw/redis++/redis_cluster.h | 1395 ++++++++++ .../include/sw/redis++/redis_cluster.hpp | 1415 +++++++++++ .../centos8/include/sw/redis++/reply.h | 363 +++ .../centos8/include/sw/redis++/sentinel.h | 138 + .../centos8/include/sw/redis++/shards.h | 115 + .../centos8/include/sw/redis++/shards_pool.h | 137 + .../centos8/include/sw/redis++/subscriber.h | 231 ++ .../centos8/include/sw/redis++/transaction.h | 77 + .../centos8/include/sw/redis++/utils.h | 269 ++ 22 files changed, 12246 insertions(+) create mode 100644 ext/hiredis-0.14.1/lib/centos8/libhiredis.a create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_args.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_options.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection_pool.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/errors.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/pipeline.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.hpp create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis++.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.hpp create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.hpp create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/reply.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/sentinel.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards_pool.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/subscriber.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/transaction.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/utils.h diff --git a/ext/hiredis-0.14.1/lib/centos8/libhiredis.a b/ext/hiredis-0.14.1/lib/centos8/libhiredis.a new file mode 100644 index 0000000000000000000000000000000000000000..0b9638798529747795a78056068d5a6a980a4f02 GIT binary patch literal 485314 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 z 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