diff --git a/Makefile b/Makefile index 1d90d7231..af9cdefc2 100644 --- a/Makefile +++ b/Makefile @@ -178,6 +178,8 @@ release-compile: release-configure $(TARGETS): release-compile release: $(TARGETS) + make -C '$(BUILD_ROOT)' test + make -C '$(BUILD_ROOT)' check shadow-configure: clean mkdir -p $(BUILD_ROOT) @@ -214,9 +216,12 @@ testnet: $(TEST_EXE): debug -test: $(TEST_EXE) +gtest: $(TEST_EXE) test x$(CROSS) = xOFF && $(TEST_EXE) || test x$(CROSS) = xON +test: gtest + $(MAKE) -C $(BUILD_ROOT) check + static-configure: $(LIBUV_PREFIX) $(LIBCURL_PREFIX) (test x$(TOOLCHAIN) = x && $(CONFIG_CMD) -DCMAKE_BUILD_TYPE=Release -DSTATIC_LINK=ON -DRELEASE_MOTTO="$(shell cat motto.txt)" -DCMAKE_C_FLAGS='$(CFLAGS)' -DCMAKE_CXX_FLAGS='$(CXXFLAGS)' -DLIBUV_ROOT='$(LIBUV_PREFIX)' -DLIBCURL_ROOT='$(LIBCURL_PREFIX)' ) || (test x$(TOOLCHAIN) != x && $(CONFIG_CMD) -DCMAKE_BUILD_TYPE=Release -DSTATIC_LINK=ON -DRELEASE_MOTTO="$(shell cat motto.txt)" -DCMAKE_C_FLAGS='$(CFLAGS)' -DCMAKE_CXX_FLAGS='$(CXXFLAGS)' -DLIBUV_ROOT='$(LIBUV_PREFIX)' -DLIBCURL_ROOT='$(LIBCURL_PREFIX)' -DCMAKE_TOOLCHAIN_FILE=$(TOOLCHAIN) -DNATIVE_BUILD=OFF ) diff --git a/llarp/dht/context.cpp b/llarp/dht/context.cpp index f7eb04603..6538ba962 100644 --- a/llarp/dht/context.cpp +++ b/llarp/dht/context.cpp @@ -50,7 +50,7 @@ namespace llarp void LookupIntroSetRecursive( const Key_t& target, const Key_t& whoasked, uint64_t whoaskedTX, - const Key_t& askpeer, uint64_t R, + const Key_t& askpeer, uint64_t recursionDepth, uint64_t relayOrder, service::EncryptedIntroSetLookupHandler result = nullptr) override; void @@ -94,7 +94,8 @@ namespace llarp void LookupIntroSetForPath(const Key_t& addr, uint64_t txid, const llarp::PathID_t& path, const Key_t& askpeer, - uint64_t R) override; + uint64_t recursionDepth, + uint64_t relayOrder) override; /// send a dht message to peer, if keepalive is true then keep the session /// with that peer alive for 10 seconds @@ -498,7 +499,7 @@ namespace llarp llarp::LogDebug("initialize dht with key ", ourKey); // start exploring - r->logic()->call_later( + router->logic()->call_later( exploreInterval, std::bind(&llarp::dht::Context::handle_explore_timer, this, exploreInterval)); @@ -541,14 +542,16 @@ namespace llarp void Context::LookupIntroSetForPath(const Key_t& addr, uint64_t txid, const llarp::PathID_t& path, - const Key_t& askpeer, uint64_t R) + const Key_t& askpeer, + uint64_t recursionDepth, uint64_t relayOrder) { TXOwner asker(OurKey(), txid); TXOwner peer(askpeer, ++ids); _pendingIntrosetLookups.NewTX( peer, asker, addr, - new LocalServiceAddressLookup(path, txid, addr, this, askpeer), - ((R + 1) * 2000)); + new LocalServiceAddressLookup(path, txid, relayOrder, addr, this, + askpeer), + ((recursionDepth + 1) * 2000)); } void @@ -568,15 +571,16 @@ namespace llarp void Context::LookupIntroSetRecursive( const Key_t& addr, const Key_t& whoasked, uint64_t txid, - const Key_t& askpeer, uint64_t R, + const Key_t& askpeer, uint64_t recursionDepth, uint64_t relayOrder, service::EncryptedIntroSetLookupHandler handler) { TXOwner asker(whoasked, txid); TXOwner peer(askpeer, ++ids); _pendingIntrosetLookups.NewTX( peer, asker, addr, - new ServiceAddressLookup(asker, addr, this, R, handler), - ((R + 1) * 2000)); + new ServiceAddressLookup(asker, addr, this, recursionDepth, + relayOrder, handler), + ((recursionDepth + 1) * 2000)); } void @@ -598,7 +602,7 @@ namespace llarp TXOwner peer(askpeer, ++ids); _pendingIntrosetLookups.NewTX( peer, asker, addr, - new ServiceAddressLookup(asker, addr, this, 0, handler), 1000); + new ServiceAddressLookup(asker, addr, this, 0, 0, handler), 1000); } bool @@ -636,11 +640,11 @@ namespace llarp } for(const auto& f : foundRouters) { - const RouterID r = f.as_array(); + const RouterID id = f.as_array(); // discard shit routers - if(router->routerProfiling().IsBadForConnect(r)) + if(router->routerProfiling().IsBadForConnect(id)) continue; - closer.emplace_back(r); + closer.emplace_back(id); } llarp::LogDebug("Gave ", closer.size(), " routers for exploration"); reply.emplace_back(new GotRouterMessage(txid, closer, false)); diff --git a/llarp/dht/context.hpp b/llarp/dht/context.hpp index 8e7abb901..e79b8e354 100644 --- a/llarp/dht/context.hpp +++ b/llarp/dht/context.hpp @@ -47,7 +47,7 @@ namespace llarp virtual void LookupIntroSetRecursive( const Key_t& target, const Key_t& whoasked, uint64_t whoaskedTX, - const Key_t& askpeer, uint64_t R, + const Key_t& askpeer, uint64_t recursionDepth, uint64_t relayOrder, service::EncryptedIntroSetLookupHandler result = service::EncryptedIntroSetLookupHandler()) = 0; @@ -69,7 +69,7 @@ namespace llarp virtual void LookupIntroSetForPath(const Key_t& addr, uint64_t txid, const PathID_t& path, const Key_t& askpeer, - uint64_t R) = 0; + uint64_t recursionDepth, uint64_t relayOrder) = 0; virtual void DHTSendTo(const RouterID& peer, IMessage* msg, bool keepalive = true) = 0; diff --git a/llarp/dht/kademlia.hpp b/llarp/dht/kademlia.hpp index 8ddda64a8..aba1d625b 100644 --- a/llarp/dht/kademlia.hpp +++ b/llarp/dht/kademlia.hpp @@ -2,6 +2,7 @@ #define LLARP_DHT_KADEMLIA_HPP #include +#include namespace llarp { @@ -20,6 +21,12 @@ namespace llarp { return (us ^ left) < (us ^ right); } + + bool + operator()(const RouterContact& left, const RouterContact& right) const + { + return (left.pubkey ^ us) < (right.pubkey ^ us); + } }; } // namespace dht } // namespace llarp diff --git a/llarp/dht/localserviceaddresslookup.cpp b/llarp/dht/localserviceaddresslookup.cpp index 4c57015d0..cc075ed60 100644 --- a/llarp/dht/localserviceaddresslookup.cpp +++ b/llarp/dht/localserviceaddresslookup.cpp @@ -12,10 +12,11 @@ namespace llarp namespace dht { LocalServiceAddressLookup::LocalServiceAddressLookup( - const PathID_t &pathid, uint64_t txid, const Key_t &addr, - AbstractContext *ctx, __attribute__((unused)) const Key_t &askpeer) - : ServiceAddressLookup(TXOwner{ctx->OurKey(), txid}, addr, ctx, 5, - nullptr) + const PathID_t &pathid, uint64_t txid, uint64_t relayOrder, + const Key_t &addr, AbstractContext *ctx, + __attribute__((unused)) const Key_t &askpeer) + : ServiceAddressLookup(TXOwner{ctx->OurKey(), txid}, addr, ctx, 2, + relayOrder, nullptr) , localPath(pathid) { } diff --git a/llarp/dht/localserviceaddresslookup.hpp b/llarp/dht/localserviceaddresslookup.hpp index 6f93f0557..04ba23530 100644 --- a/llarp/dht/localserviceaddresslookup.hpp +++ b/llarp/dht/localserviceaddresslookup.hpp @@ -14,7 +14,8 @@ namespace llarp PathID_t localPath; LocalServiceAddressLookup(const PathID_t &pathid, uint64_t txid, - const Key_t &addr, AbstractContext *ctx, + uint64_t relayOrder, const Key_t &addr, + AbstractContext *ctx, __attribute__((unused)) const Key_t &askpeer); void diff --git a/llarp/dht/message.cpp b/llarp/dht/message.cpp index 04889f1b2..922adc3ba 100644 --- a/llarp/dht/message.cpp +++ b/llarp/dht/message.cpp @@ -47,7 +47,7 @@ namespace llarp switch(*strbuf.base) { case 'F': - msg = std::make_unique< FindIntroMessage >(From, relayed); + msg = std::make_unique< FindIntroMessage >(From, relayed, 0); break; case 'R': if(relayed) diff --git a/llarp/dht/messages/findintro.cpp b/llarp/dht/messages/findintro.cpp index e3a9a7ea3..3f2eb8442 100644 --- a/llarp/dht/messages/findintro.cpp +++ b/llarp/dht/messages/findintro.cpp @@ -21,6 +21,9 @@ namespace llarp if(!BEncodeMaybeReadDictEntry("N", tagName, read, k, val)) return false; + if(!BEncodeMaybeReadDictInt("O", relayOrder, read, k, val)) + return false; + if(!BEncodeMaybeReadDictInt("R", recursionDepth, read, k, val)) return false; @@ -48,6 +51,10 @@ namespace llarp return false; if(tagName.Empty()) { + // relay order + if(!BEncodeWriteDictInt("O", relayOrder, buf)) + return false; + // recursion if(!BEncodeWriteDictInt("R", recursionDepth, buf)) return false; @@ -59,6 +66,11 @@ namespace llarp { if(!BEncodeWriteDictEntry("N", tagName, buf)) return false; + + // relay order + if(!BEncodeWriteDictInt("O", relayOrder, buf)) + return false; + // recursion if(!BEncodeWriteDictInt("R", recursionDepth, buf)) return false; @@ -117,29 +129,58 @@ namespace llarp } // we are recursive - const auto rc = dht.GetRouter()->nodedb()->FindClosestTo(location); + if(relayed) + { + uint32_t numDesired = 0; + if(relayOrder == 0) + numDesired = 2; + else if(relayOrder == 1) + numDesired = 4; + else + { + // TODO: consider forward-compatibility here + LogError("Error: relayOrder must be 0 or 1"); + return false; + } - Key_t peer = Key_t(rc.pubkey); + auto closestRCs = + dht.GetRouter()->nodedb()->FindClosestTo(location, numDesired); - if((us ^ location) <= (peer ^ location)) - { - // ask second closest as we are recursive - if(not dht.Nodes()->FindCloseExcluding(location, peer, exclude)) + // if relayOrder == 1, we want the 3rd and 4th closest, so remove the + // 1st and 2nd closest + if(relayOrder == 1) { - // no second closeset - replies.emplace_back(new GotIntroMessage({}, txID)); - return true; + auto itr = closestRCs.begin(); + std::advance(itr, 2); + closestRCs.erase(closestRCs.begin(), itr); + } + + for(const auto& entry : closestRCs) + { + Key_t peer = Key_t(entry.pubkey); + dht.LookupIntroSetForPath(location, txID, pathID, peer, + recursionDepth - 1, 0); } - } - if(relayed) - { - dht.LookupIntroSetForPath(location, txID, pathID, peer, - recursionDepth - 1); } else { + const auto rc = dht.GetRouter()->nodedb()->FindClosestTo(location); + + Key_t peer = Key_t(rc.pubkey); + + if((us ^ location) <= (peer ^ location)) + { + // ask second closest as we are recursive + if(not dht.Nodes()->FindCloseExcluding(location, peer, exclude)) + { + // no second closeset + replies.emplace_back(new GotIntroMessage({}, txID)); + return true; + } + } + dht.LookupIntroSetRecursive(location, From, txID, peer, - recursionDepth - 1); + recursionDepth - 1, 0); } return true; } diff --git a/llarp/dht/messages/findintro.hpp b/llarp/dht/messages/findintro.hpp index 107a80ce1..6d109b8fb 100644 --- a/llarp/dht/messages/findintro.hpp +++ b/llarp/dht/messages/findintro.hpp @@ -16,12 +16,15 @@ namespace llarp uint64_t recursionDepth = 0; Key_t location; llarp::service::Tag tagName; - uint64_t txID = 0; - bool relayed = false; + uint64_t txID = 0; + bool relayed = false; + uint64_t relayOrder = 0; - FindIntroMessage(const Key_t& from, bool relay) : IMessage(from) + FindIntroMessage(const Key_t& from, bool relay, uint64_t order) + : IMessage(from) { - relayed = relay; + relayed = relay; + relayOrder = order; } FindIntroMessage(const llarp::service::Tag& tag, uint64_t txid, @@ -35,11 +38,12 @@ namespace llarp } explicit FindIntroMessage(uint64_t txid, const Key_t& addr, - uint64_t maxRecursionDepth) + uint64_t maxRecursionDepth, uint64_t order) : IMessage({}) , recursionDepth(maxRecursionDepth) , location(addr) , txID(txid) + , relayOrder(order) { tagName.Zero(); } diff --git a/llarp/dht/serviceaddresslookup.cpp b/llarp/dht/serviceaddresslookup.cpp index 0ad304226..e832bebf9 100644 --- a/llarp/dht/serviceaddresslookup.cpp +++ b/llarp/dht/serviceaddresslookup.cpp @@ -11,10 +11,12 @@ namespace llarp { ServiceAddressLookup::ServiceAddressLookup( const TXOwner &asker, const Key_t &addr, AbstractContext *ctx, - uint64_t r, service::EncryptedIntroSetLookupHandler handler) + uint64_t recursion, uint32_t order, + service::EncryptedIntroSetLookupHandler handler) : TX< Key_t, service::EncryptedIntroSet >(asker, addr, ctx) , handleResult(std::move(handler)) - , R(r) + , recursionDepth(recursion) + , relayOrder(order) { peersAsked.insert(ctx->OurKey()); } @@ -52,17 +54,18 @@ namespace llarp void ServiceAddressLookup::Start(const TXOwner &peer) { - parent->DHTSendTo(peer.node.as_array(), - new FindIntroMessage(peer.txid, target, R)); + parent->DHTSendTo( + peer.node.as_array(), + new FindIntroMessage(peer.txid, target, recursionDepth, relayOrder)); } void ServiceAddressLookup::DoNextRequest(const Key_t &ask) { - if(R) + if(recursionDepth) { parent->LookupIntroSetRecursive(target, whoasked.node, whoasked.txid, - ask, R - 1); + ask, recursionDepth - 1, relayOrder); } else { diff --git a/llarp/dht/serviceaddresslookup.hpp b/llarp/dht/serviceaddresslookup.hpp index 5158dfd2c..3f1f7a2c1 100644 --- a/llarp/dht/serviceaddresslookup.hpp +++ b/llarp/dht/serviceaddresslookup.hpp @@ -15,10 +15,12 @@ namespace llarp struct ServiceAddressLookup : public TX< Key_t, service::EncryptedIntroSet > { service::EncryptedIntroSetLookupHandler handleResult; - uint64_t R; + uint64_t recursionDepth; + uint32_t relayOrder; ServiceAddressLookup(const TXOwner &asker, const Key_t &addr, - AbstractContext *ctx, uint64_t r, + AbstractContext *ctx, uint64_t recursionDepth, + uint32_t relayOrder, service::EncryptedIntroSetLookupHandler handler); bool diff --git a/llarp/dht/taglookup.cpp b/llarp/dht/taglookup.cpp index 0097e851d..0924ca78b 100644 --- a/llarp/dht/taglookup.cpp +++ b/llarp/dht/taglookup.cpp @@ -28,8 +28,9 @@ namespace llarp void TagLookup::Start(const TXOwner &peer) { - parent->DHTSendTo(peer.node.as_array(), - new FindIntroMessage(target, peer.txid, R)); + parent->DHTSendTo( + peer.node.as_array(), + new FindIntroMessage(target, peer.txid, recursionDepth)); } void diff --git a/llarp/dht/taglookup.hpp b/llarp/dht/taglookup.hpp index d6f2c0a16..88c3dfd95 100644 --- a/llarp/dht/taglookup.hpp +++ b/llarp/dht/taglookup.hpp @@ -11,11 +11,11 @@ namespace llarp { struct TagLookup : public TX< service::Tag, service::EncryptedIntroSet > { - uint64_t R; + uint64_t recursionDepth; TagLookup(const TXOwner &asker, const service::Tag &tag, - AbstractContext *ctx, uint64_t r) + AbstractContext *ctx, uint64_t recursion) : TX< service::Tag, service::EncryptedIntroSet >(asker, tag, ctx) - , R(r) + , recursionDepth(recursion) { } diff --git a/llarp/nodedb.cpp b/llarp/nodedb.cpp index 35d016e6a..7f3309b52 100644 --- a/llarp/nodedb.cpp +++ b/llarp/nodedb.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -11,6 +12,7 @@ #include #include +#include #include #include #include @@ -112,6 +114,32 @@ llarp_nodedb::FindClosestTo(const llarp::dht::Key_t &location) return rc; } +std::vector< llarp::RouterContact > +llarp_nodedb::FindClosestTo(const llarp::dht::Key_t &location, + uint32_t numRouters) +{ + llarp::util::Lock lock(&access); + std::vector< const llarp::RouterContact * > all; + + all.reserve(entries.size()); + for(auto &entry : entries) + { + all.push_back(&entry.second.rc); + } + + auto it_mid = numRouters < all.size() ? all.begin() + numRouters : all.end(); + std::partial_sort(all.begin(), it_mid, all.end(), + [compare = llarp::dht::XorMetric{location}]( + auto *a, auto *b) { return compare(*a, *b); }); + + std::vector< llarp::RouterContact > closest; + closest.reserve(numRouters); + for(auto it = all.begin(); it != it_mid; ++it) + closest.push_back(**it); + + return closest; +} + /// skiplist directory is hex encoded first nibble /// skiplist filename is .snode.signed std::string diff --git a/llarp/nodedb.hpp b/llarp/nodedb.hpp index 820615c87..721d18655 100644 --- a/llarp/nodedb.hpp +++ b/llarp/nodedb.hpp @@ -77,6 +77,10 @@ struct llarp_nodedb llarp::RouterContact FindClosestTo(const llarp::dht::Key_t &location); + /// find the $numRouters closest routers to the given DHT key + std::vector< llarp::RouterContact > + FindClosestTo(const llarp::dht::Key_t &location, uint32_t numRouters); + /// return true if we should save our nodedb to disk bool ShouldSaveToDisk(llarp_time_t now = 0) const; diff --git a/llarp/path/path.cpp b/llarp/path/path.cpp index d76e8bfc7..b9f295a9a 100644 --- a/llarp/path/path.cpp +++ b/llarp/path/path.cpp @@ -50,7 +50,8 @@ namespace llarp // initialize parts of the introduction intro.router = hops[hsz - 1].rc.pubkey; intro.pathID = hops[hsz - 1].txID; - EnterState(ePathBuilding, parent->Now()); + if(parent) + EnterState(ePathBuilding, parent->Now()); } bool diff --git a/llarp/path/path.hpp b/llarp/path/path.hpp index ae3bcad98..c6a426dbb 100644 --- a/llarp/path/path.hpp +++ b/llarp/path/path.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -127,10 +128,11 @@ namespace llarp } }; + /// hash for std::shared_ptr struct Ptr_Hash { size_t - operator()(const std::shared_ptr< Path >& p) const + operator()(const Path_ptr& p) const { if(p == nullptr) return 0; @@ -138,6 +140,32 @@ namespace llarp } }; + /// hash for std::shared_ptr by path endpoint + struct Endpoint_Hash + { + size_t + operator()(const Path_ptr& p) const + { + if(p == nullptr) + return 0; + return RouterID::Hash{}(p->Endpoint()); + } + }; + + /// comparision for equal endpoints + struct Endpoint_Equals + { + bool + operator()(const Path_ptr& left, const Path_ptr& right) const + { + return left && right && left->Endpoint() == left->Endpoint(); + } + }; + + /// unordered set of paths with unique endpoints + using UniqueEndpointSet_t = + std::unordered_set< Path_ptr, Endpoint_Hash, Endpoint_Equals >; + bool operator<(const Path& other) const { diff --git a/llarp/service/endpoint.cpp b/llarp/service/endpoint.cpp index 6cd08871f..eb62f0074 100644 --- a/llarp/service/endpoint.cpp +++ b/llarp/service/endpoint.cpp @@ -460,9 +460,20 @@ namespace llarp bool Endpoint::PublishIntroSet(const EncryptedIntroSet& i, AbstractRouter* r) { - // publish via near router - const auto path = GetEstablishedPathClosestTo(i.derivedSigningKey); - return path && PublishIntroSetVia(i, r, path); + /// number of routers to publish to + static constexpr size_t PublishRedundancy = 2; + const auto paths = + GetManyPathsWithUniqueEndpoints(this, PublishRedundancy); + // do publishing for each path selected + size_t published = 0; + for(const auto& path : paths) + { + if(PublishIntroSetVia(i, r, path)) + { + published++; + } + } + return published == PublishRedundancy; } struct PublishIntroSetJob : public IServiceLookup @@ -938,22 +949,9 @@ namespace llarp bool Endpoint::EnsurePathToService(const Address remote, PathEnsureHook hook, - ABSL_ATTRIBUTE_UNUSED llarp_time_t timeoutMS, - bool randomPath) + ABSL_ATTRIBUTE_UNUSED llarp_time_t timeoutMS) { - const dht::Key_t location = remote.ToKey(); - path::Path_ptr path = nullptr; - if(randomPath) - path = PickRandomEstablishedPath(); - else - path = GetEstablishedPathClosestTo(location.as_array()); - if(!path) - { - LogWarn("No outbound path for lookup yet"); - BuildOne(); - return false; - } - + static constexpr size_t NumParalellLookups = 2; LogInfo(Name(), " Ensure Path to ", remote.ToString()); auto& sessions = m_state->m_RemoteSessions; @@ -969,23 +967,28 @@ namespace llarp auto& lookups = m_state->m_PendingServiceLookups; - if(lookups.count(remote) >= MaxConcurrentLookups) - { - path = PickRandomEstablishedPath(); - } + const auto paths = + GetManyPathsWithUniqueEndpoints(this, NumParalellLookups); using namespace std::placeholders; - HiddenServiceAddressLookup* job = new HiddenServiceAddressLookup( - this, util::memFn(&Endpoint::OnLookup, this), location, - PubKey{remote.as_array()}, GenTXID()); - LogInfo("doing lookup for ", remote, " via ", path->Endpoint()); - if(job->SendRequestViaPath(path, Router())) + size_t lookedUp = 0; + const dht::Key_t location = remote.ToKey(); + for(const auto& path : paths) { - lookups.emplace(remote, hook); - return true; + HiddenServiceAddressLookup* job = new HiddenServiceAddressLookup( + this, util::memFn(&Endpoint::OnLookup, this), location, + PubKey{remote.as_array()}, 0, GenTXID()); + LogInfo("doing lookup for ", remote, " via ", path->Endpoint(), " at ", + location); + if(job->SendRequestViaPath(path, Router())) + { + lookups.emplace(remote, hook); + lookedUp++; + } + else + LogError(Name(), " send via path failed for lookup"); } - LogError("send via path failed"); - return false; + return lookedUp == NumParalellLookups; } bool @@ -1216,7 +1219,7 @@ namespace llarp [self = this](Address addr, OutboundContext* ctx) { if(ctx) { - ctx->UpdateIntroSet(true); + ctx->UpdateIntroSet(); for(auto& pending : self->m_state->m_PendingTraffic[addr]) { ctx->AsyncEncryptAndSendTo(pending.Buffer(), diff --git a/llarp/service/endpoint.hpp b/llarp/service/endpoint.hpp index c67dc83d1..3f4d2cd2f 100644 --- a/llarp/service/endpoint.hpp +++ b/llarp/service/endpoint.hpp @@ -289,7 +289,7 @@ namespace llarp /// address bool EnsurePathToService(const Address remote, PathEnsureHook h, - uint64_t timeoutMS, bool lookupOnRandomPath = true); + uint64_t timeoutMS); using SNodeEnsureHook = std::function< void(const RouterID, exit::BaseSession_ptr) >; diff --git a/llarp/service/endpoint_util.hpp b/llarp/service/endpoint_util.hpp index f5045e06c..ab7dff06e 100644 --- a/llarp/service/endpoint_util.hpp +++ b/llarp/service/endpoint_util.hpp @@ -41,6 +41,21 @@ namespace llarp GetConvoTagsForService(const ConvoMap& sessions, const Address& addr, std::set< ConvoTag >& tags); }; + + template < typename Endpoint_t > + static path::Path::UniqueEndpointSet_t + GetManyPathsWithUniqueEndpoints(Endpoint_t* ep, size_t N, size_t tries = 10) + { + path::Path::UniqueEndpointSet_t paths; + do + { + --tries; + const auto path = ep->PickRandomEstablishedPath(); + if(path) + paths.emplace(path); + } while(tries > 0 and paths.size() < N); + return paths; + } } // namespace service } // namespace llarp diff --git a/llarp/service/hidden_service_address_lookup.cpp b/llarp/service/hidden_service_address_lookup.cpp index 49a63ff38..193a445fa 100644 --- a/llarp/service/hidden_service_address_lookup.cpp +++ b/llarp/service/hidden_service_address_lookup.cpp @@ -8,13 +8,12 @@ namespace llarp { namespace service { - HiddenServiceAddressLookup::HiddenServiceAddressLookup(Endpoint* p, - HandlerFunc h, - const dht::Key_t& l, - const PubKey& k, - uint64_t tx) + HiddenServiceAddressLookup::HiddenServiceAddressLookup( + Endpoint* p, HandlerFunc h, const dht::Key_t& l, const PubKey& k, + uint64_t order, uint64_t tx) : IServiceLookup(p, tx, "HSLookup") , rootkey(k) + , relayOrder(order) , location(l) , handle(std::move(h)) { @@ -46,8 +45,8 @@ namespace llarp HiddenServiceAddressLookup::BuildRequestMessage() { auto msg = std::make_shared< routing::DHTMessage >(); - msg->M.emplace_back( - std::make_unique< dht::FindIntroMessage >(txid, location, 2)); + msg->M.emplace_back(std::make_unique< dht::FindIntroMessage >( + txid, location, 2, relayOrder)); return msg; } diff --git a/llarp/service/hidden_service_address_lookup.hpp b/llarp/service/hidden_service_address_lookup.hpp index 075f94488..fcba8fdf0 100644 --- a/llarp/service/hidden_service_address_lookup.hpp +++ b/llarp/service/hidden_service_address_lookup.hpp @@ -13,6 +13,7 @@ namespace llarp struct HiddenServiceAddressLookup : public IServiceLookup { const PubKey rootkey; + uint64_t relayOrder; const dht::Key_t location; using HandlerFunc = std::function< bool( const Address&, absl::optional< const IntroSet >, const RouterID&) >; @@ -20,7 +21,8 @@ namespace llarp HiddenServiceAddressLookup(Endpoint* p, HandlerFunc h, const dht::Key_t& location, - const PubKey& rootkey, uint64_t tx); + const PubKey& rootkey, uint64_t relayOrder, + uint64_t tx); ~HiddenServiceAddressLookup() override = default; diff --git a/llarp/service/outbound_context.cpp b/llarp/service/outbound_context.cpp index 3fded4796..2d84078de 100644 --- a/llarp/service/outbound_context.cpp +++ b/llarp/service/outbound_context.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include #include @@ -48,7 +50,7 @@ namespace llarp { SwapIntros(); } - UpdateIntroSet(false); + UpdateIntroSet(); } return true; } @@ -220,38 +222,24 @@ namespace llarp } void - OutboundContext::UpdateIntroSet(bool randomizePath) + OutboundContext::UpdateIntroSet() { if(updatingIntroSet || markedBad) return; const auto addr = currentIntroSet.A.Addr(); - path::Path_ptr path = nullptr; - if(randomizePath) - { - path = m_Endpoint->PickRandomEstablishedPath(); - } - else - path = m_Endpoint->GetEstablishedPathClosestTo(addr.as_array()); - - if(path == nullptr) - { - path = PickRandomEstablishedPath(); - } - - if(path) + const auto paths = GetManyPathsWithUniqueEndpoints(this, 2); + uint64_t relayOrder = 0; + for(const auto& path : paths) { HiddenServiceAddressLookup* job = new HiddenServiceAddressLookup( m_Endpoint, util::memFn(&OutboundContext::OnIntroSetUpdate, shared_from_this()), - location, PubKey{addr.as_array()}, m_Endpoint->GenTXID()); - - updatingIntroSet = job->SendRequestViaPath(path, m_Endpoint->Router()); - } - else - { - LogWarn("Cannot update introset no path for outbound session to ", - currentIntroSet.A.Addr().ToString()); + location, PubKey{addr.as_array()}, relayOrder, + m_Endpoint->GenTXID()); + relayOrder++; + if(job->SendRequestViaPath(path, m_Endpoint->Router())) + updatingIntroSet = true; } } @@ -307,7 +295,7 @@ namespace llarp } if(currentIntroSet.HasExpiredIntros(now)) { - UpdateIntroSet(false); + UpdateIntroSet(); } // send control message if we look too quiet if(lastGoodSend) @@ -419,7 +407,7 @@ namespace llarp { // update introset LogInfo(Name(), " updating introset"); - UpdateIntroSet(false); + UpdateIntroSet(); return true; } return false; @@ -494,7 +482,7 @@ namespace llarp OutboundContext::HandlePathDied(path::Path_ptr path) { // unconditionally update introset - UpdateIntroSet(false); + UpdateIntroSet(); const RouterID endpoint(path->Endpoint()); // if a path to our current intro died... if(endpoint == remoteIntro.router) diff --git a/llarp/service/outbound_context.hpp b/llarp/service/outbound_context.hpp index 3fee387e1..0468537da 100644 --- a/llarp/service/outbound_context.hpp +++ b/llarp/service/outbound_context.hpp @@ -88,7 +88,7 @@ namespace llarp /// issues a lookup to find the current intro set of the remote service void - UpdateIntroSet(bool randomizePath) override; + UpdateIntroSet() override; void HandlePathBuilt(path::Path_ptr path) override; diff --git a/llarp/service/sendcontext.hpp b/llarp/service/sendcontext.hpp index 4d957776d..3cbb0e674 100644 --- a/llarp/service/sendcontext.hpp +++ b/llarp/service/sendcontext.hpp @@ -61,7 +61,7 @@ namespace llarp } virtual void - UpdateIntroSet(bool randomizePath = false) = 0; + UpdateIntroSet() = 0; virtual bool MarkCurrentIntroBad(llarp_time_t now) = 0; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index df7bfa946..b65777eb9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,6 @@ set(TEST_EXE testAll) +set(CHECK_EXE checkAll) + list(APPEND TEST_SRC config/test_llarp_config_config.cpp @@ -74,3 +76,13 @@ endif(NOT WIN32) if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") target_link_directories(${TEST_EXE} PRIVATE /usr/local/lib) endif() + +add_executable(${CHECK_EXE} + nodedb/test_nodedb.cpp + path/test_path.cpp + check_main.cpp) + +target_link_libraries(${CHECK_EXE} PUBLIC ${STATIC_LIB}) +target_include_directories(${CHECK_EXE} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +add_custom_target(check COMMAND ${CHECK_EXE}) diff --git a/test/catch.hpp b/test/catch.hpp new file mode 100644 index 000000000..6c1756a6c --- /dev/null +++ b/test/catch.hpp @@ -0,0 +1,17615 @@ +/* + * Catch v2.11.1 + * Generated: 2019-12-28 21:22:11.930976 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2019 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 11 +#define CATCH_VERSION_PATCH 1 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +#ifdef __APPLE__ +# include +# if TARGET_OS_OSX == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +// We have to avoid both ICC and Clang, because they try to mask themselves +// as gcc, and we want only GCC in this block +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) +#endif + +#if defined(__clang__) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) + +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(__clang__) // Handle Clang masquerading for msvc +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif + +#if defined(__UCLIBC__) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + + bool empty() const noexcept { return file[0] == '\0'; } + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); + } + + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception + auto c_str() const -> char const*; + + public: // substrings and searches + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; + + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; + + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } + + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + }; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template