diff --git a/src/core/router/net_db/impl.cc b/src/core/router/net_db/impl.cc index 11fa9d7f..1bef84d3 100644 --- a/src/core/router/net_db/impl.cc +++ b/src/core/router/net_db/impl.cc @@ -946,62 +946,77 @@ std::shared_ptr NetDb::GetClosestFloodfill( const IdentHash& destination, const std::set& excluded) const { std::shared_ptr r; - XORMetric min_metric; - IdentHash dest_key = CreateRoutingKey(destination); - min_metric.SetMax(); - std::unique_lock l(m_FloodfillsMutex); - for (auto it : m_Floodfills) { - if (!it->IsUnreachable()) { - XORMetric m = dest_key ^ it->GetIdentHash(); - if (m < min_metric && !excluded.count(it->GetIdentHash())) { - min_metric = m; - r = it; - } + try + { + XORMetric min_metric; + IdentHash dest_key = CreateRoutingKey(destination); + min_metric.SetMax(); + std::unique_lock l(m_FloodfillsMutex); + for (const auto& it : m_Floodfills) + { + if (!it->IsUnreachable() && !excluded.count(it->GetIdentHash())) + { + XORMetric m = dest_key ^ it->GetIdentHash(); + if (m < min_metric) + { + min_metric = m; + r = it; + } + } + } + } + catch (...) + { + m_Exception.Dispatch(__func__); + throw; } - } return r; } std::vector NetDb::GetClosestFloodfills( const IdentHash& destination, - std::uint8_t num, - std::set& excluded) const + const std::uint8_t num, + const std::set& excluded) const { - struct Sorted { - std::shared_ptr r; - XORMetric metric; - bool operator<(const Sorted& other) const { - return metric < other.metric; - } - }; - std::set sorted; - IdentHash dest_key = CreateRoutingKey(destination); { - std::unique_lock l(m_FloodfillsMutex); - for (auto it : m_Floodfills) { - if (!it->IsUnreachable()) { - XORMetric m = dest_key ^ it->GetIdentHash(); - if (sorted.size() < num) { - sorted.insert({it, m}); - } else if (m < sorted.rbegin()->metric) { - sorted.insert({it, m}); - sorted.erase(std::prev(sorted.end())); + std::vector res; + try + { + IdentHash dest_key = CreateRoutingKey(destination); + struct Sorted + { + std::shared_ptr r; + XORMetric metric; + bool operator<(const Sorted& other) const + { + return metric < other.metric; } + }; + std::set sorted; + { + std::unique_lock l(m_FloodfillsMutex); + for (const auto& it : m_Floodfills) + { + if (!it->IsUnreachable() && !excluded.count(it->GetIdentHash())) + { + XORMetric m = dest_key ^ it->GetIdentHash(); + sorted.insert({it, m}); + } + } } + + std::uint8_t i{}; + for (auto it = sorted.begin(); it != sorted.end() && i < num; ++it) + { + const auto& ident = it->r->GetIdentHash(); + res.push_back(ident); + ++i; + } } - } - std::vector res; - std::uint8_t i{}; - for (auto it : sorted) { - if (i < num) { - auto& ident = it.r->GetIdentHash(); - if (!excluded.count(ident)) { - res.push_back(ident); - i++; - } - } else { - break; + catch (...) + { + m_Exception.Dispatch(__func__); + throw; } - } return res; } @@ -1009,19 +1024,31 @@ std::shared_ptr NetDb::GetClosestNonFloodfill( const IdentHash& destination, const std::set& excluded) const { std::shared_ptr r; - XORMetric min_metric; - IdentHash dest_key = CreateRoutingKey(destination); - min_metric.SetMax(); - // must be called from NetDb thread only - for (auto it : m_RouterInfos) { - if (!it.second->HasCap(RouterInfo::Cap::Floodfill)) { - XORMetric m = dest_key ^ it.first; - if (m < min_metric && !excluded.count(it.first)) { - min_metric = m; - r = it.second; - } + try + { + XORMetric min_metric; + IdentHash dest_key = CreateRoutingKey(destination); + min_metric.SetMax(); + std::unique_lock l(m_RouterInfosMutex); + for (const auto& it : m_RouterInfos) + { + if (!it.second->HasCap(RouterInfo::Cap::Floodfill) + && !excluded.count(it.first)) + { + XORMetric m = dest_key ^ it.first; + if (m < min_metric) + { + min_metric = m; + r = it.second; + } + } + } + } + catch (...) + { + m_Exception.Dispatch(__func__); + throw; } - } return r; } diff --git a/src/core/router/net_db/impl.h b/src/core/router/net_db/impl.h index c71fdfec..b9d9d19a 100644 --- a/src/core/router/net_db/impl.h +++ b/src/core/router/net_db/impl.h @@ -197,16 +197,16 @@ class NetDb : public NetDbTraits { std::shared_ptr GetClosestFloodfill( const IdentHash& destination, - const std::set& excluded) const; + const std::set& excluded = std::set()) const; std::vector GetClosestFloodfills( const IdentHash& destination, - std::uint8_t num, - std::set& excluded) const; + const std::uint8_t num, + const std::set& excluded = std::set()) const; std::shared_ptr GetClosestNonFloodfill( const IdentHash& destination, - const std::set& excluded) const; + const std::set& excluded = std::set()) const; void SetUnreachable( const IdentHash& ident, diff --git a/src/core/util/exception.cc b/src/core/util/exception.cc index fc428b0b..bf139a66 100644 --- a/src/core/util/exception.cc +++ b/src/core/util/exception.cc @@ -45,7 +45,7 @@ namespace core { Exception::Exception(const char* message) : m_Message(message) {} // TODO(anonimal): exception error codes to replace strings? -void Exception::Dispatch(const char* message) +void Exception::Dispatch(const char* message) const { // Message to log std::string log; diff --git a/src/core/util/exception.h b/src/core/util/exception.h index dc84d926..e7ae5cc1 100644 --- a/src/core/util/exception.h +++ b/src/core/util/exception.h @@ -49,7 +49,7 @@ class Exception final { /// @brief Exception class dispatcher /// @details Set optional exception message, concats messages, adds trivial formatting /// @param message String message to log for exception - void Dispatch(const char* message = ""); + void Dispatch(const char* message = "") const; private: std::string m_Message; diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index d062af32..142afef2 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(kovri-tests core/crypto/rand.cc core/crypto/util/x509.cc core/router/identity.cc + core/router/net_db/impl.cc core/router/transports/ssu/packet.cc core/util/byte_stream.cc core/util/config.cc diff --git a/tests/unit_tests/core/router/net_db/impl.cc b/tests/unit_tests/core/router/net_db/impl.cc new file mode 100644 index 00000000..232da332 --- /dev/null +++ b/tests/unit_tests/core/router/net_db/impl.cc @@ -0,0 +1,170 @@ +/** // + * Copyright (c) 2015-2018, The Kovri I2P Router Project // + * // + * All rights reserved. // + * // + * 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. // + * // + * 3. Neither the name of the copyright holder 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 HOLDER 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. // + */ + +#define BOOST_TEST_DYN_LINK + +#include + +#include +#include +#include +#include + +#include "core/router/identity.h" +#include "core/router/info.h" +#include "core/router/net_db/impl.h" + +#include "tests/unit_tests/core/router/identity.h" + +namespace core = kovri::core; + +struct NetDbFixture : public IdentityExFixture +{ + using Cap = core::RouterInfoTraits::Cap; + + NetDbFixture() : m_NetDb(std::make_unique()) + { + // Use Alice's data from IdentityEx fixture + core::IdentityEx m_Ident; + m_Ident.FromBuffer(m_AliceIdentity.data(), m_AliceIdentity.size()); + m_Hash = m_Ident.GetIdentHash(); + } + + std::unique_ptr AddRouterInfo(Cap cap) + { + auto ri = std::make_unique( + core::PrivateKeys::CreateRandomKeys( + core::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519), + std::vector>{{"127.0.0.1", 9111}}, + std::make_pair(true, false), + cap); + + m_NetDb->AddRouterInfo( + ri->GetIdentHash(), ri->GetBuffer(), ri->GetBufferLen()); + + return ri; + } + + std::shared_ptr GetClosestFloodfill() + { + auto ff = m_NetDb->GetClosestFloodfill(m_Hash); + if (!ff) + throw std::runtime_error("no floodfill available"); + return ff; + } + + std::vector GetClosestFloodfills(const std::uint8_t count) + { + auto ffs = m_NetDb->GetClosestFloodfills(m_Hash, count); + if (ffs.empty()) + throw std::runtime_error("no floodfills available"); + return ffs; + } + + std::shared_ptr GetClosestNonFloodfill() + { + auto ri = m_NetDb->GetClosestNonFloodfill(m_Hash); + if (!ri) + throw std::runtime_error("no routers available"); + return ri; + } + + core::IdentHash m_Hash; + std::unique_ptr m_NetDb; +}; + +BOOST_FIXTURE_TEST_SUITE(NetDbTests, NetDbFixture) + +// TODO(unassigned): this isn't an accurate testcase (we should rather test kademlia) +BOOST_AUTO_TEST_CASE(ValidClosestFloodfills) +{ + constexpr std::uint8_t count(2); // FF count + + // Add floodfills to netdb + std::set> infos; + for (auto i(0); i < count; i++) + infos.insert(AddRouterInfo(Cap::Floodfill)); + + // Get added floodfill hashes + std::vector hashes; + for (const auto& ri : infos) + hashes.push_back(ri->GetIdentHash()); + + // Get closest floodfills + std::vector ffs; + BOOST_REQUIRE_NO_THROW(ffs = GetClosestFloodfills(infos.size())); + + // Floodfill hashes should match added router hashes + // TODO(unassigned): this should change once we include the kademlia test + std::sort(ffs.begin(), ffs.end()); + std::sort(hashes.begin(), hashes.end()); + + BOOST_CHECK(ffs == hashes); +} + +BOOST_AUTO_TEST_CASE(ValidClosestFloodfill) +{ + std::unique_ptr ri; + BOOST_REQUIRE_NO_THROW(ri = AddRouterInfo(Cap::Floodfill)); + + std::shared_ptr ff; + BOOST_REQUIRE_NO_THROW(ff = GetClosestFloodfill()); + + BOOST_REQUIRE_EQUAL(ff->GetIdentHash(), ri->GetIdentHash()); +} + +BOOST_AUTO_TEST_CASE(ValidClosestNonFloodfill) +{ + std::unique_ptr ri; + BOOST_REQUIRE_NO_THROW(ri = AddRouterInfo(Cap::HighBandwidth)); + + std::shared_ptr ff; + BOOST_REQUIRE_NO_THROW(ff = GetClosestNonFloodfill()); + + BOOST_CHECK_EQUAL(ff->GetIdentHash(), ri->GetIdentHash()); +} + +BOOST_AUTO_TEST_CASE(InvalidRouters) +{ + core::IdentHash hash; // Empty hash + + BOOST_CHECK_THROW(m_NetDb->GetClosestFloodfill(hash), std::exception); + BOOST_CHECK_THROW(m_NetDb->GetClosestFloodfill(nullptr), std::exception); + + BOOST_CHECK_THROW(m_NetDb->GetClosestFloodfills(hash, 1), std::exception); + BOOST_CHECK_THROW(m_NetDb->GetClosestFloodfills(nullptr, 1), std::exception); + + BOOST_CHECK_THROW(GetClosestFloodfills(0), std::exception); + + BOOST_CHECK_THROW(m_NetDb->GetClosestNonFloodfill(nullptr), std::exception); + BOOST_CHECK_THROW(m_NetDb->GetClosestNonFloodfill(hash), std::exception); +} + +BOOST_AUTO_TEST_SUITE_END()