rib: provide self-learning helpers
refs #4683
Change-Id: Ib1e586a505d07a5e1f7a4b6d78306ea08b4dcda8
diff --git a/tests/identity-management-fixture.cpp b/tests/identity-management-fixture.cpp
index a2977f8..ab361fd 100644
--- a/tests/identity-management-fixture.cpp
+++ b/tests/identity-management-fixture.cpp
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2017, Regents of the University of California,
+/*
+ * Copyright (c) 2014-2018, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -27,6 +27,7 @@
#include <ndn-cxx/security/pib/identity.hpp>
#include <ndn-cxx/security/pib/key.hpp>
#include <ndn-cxx/security/pib/pib.hpp>
+#include <ndn-cxx/security/transform.hpp>
#include <ndn-cxx/security/v2/certificate.hpp>
#include <ndn-cxx/util/io.hpp>
#include <boost/filesystem.hpp>
@@ -84,5 +85,27 @@
}
}
+std::string
+IdentityManagementFixture::getIdentityCertificateBase64(const Name& identity, bool wantAdd)
+{
+ ndn::security::v2::Certificate cert;
+ try {
+ cert = m_keyChain.getPib().getIdentity(identity).getDefaultKey().getDefaultCertificate();
+ }
+ catch (const ndn::security::Pib::Error&) {
+ if (!wantAdd) {
+ BOOST_THROW_EXCEPTION(std::runtime_error("identity does not exist"));
+ }
+ cert = m_keyChain.createIdentity(identity).getDefaultKey().getDefaultCertificate();
+ }
+
+ Block wire = cert.wireEncode();
+
+ std::ostringstream oss;
+ namespace tr = ndn::security::transform;
+ tr::bufferSource(wire.wire(), wire.size()) >> tr::base64Encode(false) >> tr::streamSink(oss);
+ return oss.str();
+}
+
} // namespace tests
} // namespace nfd
diff --git a/tests/identity-management-fixture.hpp b/tests/identity-management-fixture.hpp
index c9d054b..0dbf333 100644
--- a/tests/identity-management-fixture.hpp
+++ b/tests/identity-management-fixture.hpp
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2017, Regents of the University of California,
+/*
+ * Copyright (c) 2014-2018, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -59,6 +59,14 @@
bool
saveIdentityCertificate(const Name& identity, const std::string& filename, bool wantAdd = false);
+ /** \brief retrieve identity certificate as base64 string
+ * \param identity identity name
+ * \param wantAdd if true, add new identity when necessary
+ * \throw std::runtime_error identity does not exist and wantAdd is false
+ */
+ std::string
+ getIdentityCertificateBase64(const Name& identity, bool wantAdd = false);
+
protected:
ndn::KeyChain m_keyChain;
diff --git a/tests/rib/rib-manager.t.cpp b/tests/rib/rib-manager.t.cpp
index 6966018..8ccde7d 100644
--- a/tests/rib/rib-manager.t.cpp
+++ b/tests/rib/rib-manager.t.cpp
@@ -63,7 +63,7 @@
, m_status(status)
, m_nfdController(m_face, m_keyChain)
, m_fibUpdater(m_rib, m_nfdController)
- , m_manager(m_rib, m_face, m_nfdController, m_dispatcher)
+ , m_manager(m_rib, m_face, m_keyChain, m_nfdController, m_dispatcher)
{
m_rib.mockFibResponse = [] (const RibUpdateBatch& batch) {
BOOST_CHECK(batch.begin() != batch.end());
diff --git a/tests/rib/sl-announce.t.cpp b/tests/rib/sl-announce.t.cpp
new file mode 100644
index 0000000..c77b91a
--- /dev/null
+++ b/tests/rib/sl-announce.t.cpp
@@ -0,0 +1,331 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018, Regents of the University of California,
+ * Arizona Board of Regents,
+ * Colorado State University,
+ * University Pierre & Marie Curie, Sorbonne University,
+ * Washington University in St. Louis,
+ * Beijing Institute of Technology,
+ * The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rib/rib-manager.hpp"
+
+#include "tests/identity-management-fixture.hpp"
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+namespace nfd {
+namespace rib {
+namespace tests {
+
+using namespace nfd::tests;
+
+class RibManagerSlAnnounceFixture : public IdentityManagementTimeFixture
+{
+public:
+ using SlAnnounceResult = RibManager::SlAnnounceResult;
+
+ RibManagerSlAnnounceFixture()
+ : m_face(getGlobalIoService(), m_keyChain)
+ , m_nfdController(m_face, m_keyChain)
+ , m_dispatcher(m_face, m_keyChain)
+ , m_fibUpdater(rib, m_nfdController)
+ , m_trustedSigner(m_keyChain.createIdentity("/trusted", ndn::RsaKeyParams()))
+ , m_untrustedSigner(m_keyChain.createIdentity("/untrusted", ndn::RsaKeyParams()))
+ {
+ rib.mockFibResponse = [] (const RibUpdateBatch& batch) { return true; };
+ rib.wantMockFibResponseOnce = false;
+
+ // Face, Controller, Dispatcher are irrelevant to SlAnnounce functions but required by
+ // RibManager construction, so they are private. RibManager is a pointer to avoid code style
+ // rule 1.4 violation.
+ manager = make_unique<RibManager>(rib, m_face, m_keyChain, m_nfdController, m_dispatcher);
+
+ loadTrustSchema();
+ }
+
+ template<typename ...T>
+ ndn::PrefixAnnouncement
+ makeTrustedAnn(const T&... args)
+ {
+ return signPrefixAnn(makePrefixAnn(args...), m_keyChain, m_trustedSigner);
+ }
+
+ template<typename ...T>
+ ndn::PrefixAnnouncement
+ makeUntrustedAnn(const T&... args)
+ {
+ return signPrefixAnn(makePrefixAnn(args...), m_keyChain, m_untrustedSigner);
+ }
+
+ /** \brief Invoke manager->slAnnounce and wait for result.
+ */
+ SlAnnounceResult
+ slAnnounceSync(const ndn::PrefixAnnouncement& pa, uint64_t faceId, time::milliseconds maxLifetime)
+ {
+ optional<SlAnnounceResult> result;
+ manager->slAnnounce(pa, faceId, maxLifetime,
+ [&] (RibManager::SlAnnounceResult res) {
+ BOOST_CHECK(!result);
+ result = res;
+ });
+
+ getGlobalIoService().poll();
+ BOOST_CHECK(result);
+ return result.value_or(SlAnnounceResult::ERROR);
+ }
+
+ /** \brief Invoke manager->slRenew and wait for result.
+ */
+ SlAnnounceResult
+ slRenewSync(const Name& name, uint64_t faceId, time::milliseconds maxLifetime)
+ {
+ optional<SlAnnounceResult> result;
+ manager->slRenew(name, faceId, maxLifetime,
+ [&] (RibManager::SlAnnounceResult res) {
+ BOOST_CHECK(!result);
+ result = res;
+ });
+
+ getGlobalIoService().poll();
+ BOOST_CHECK(result);
+ return result.value_or(SlAnnounceResult::ERROR);
+ }
+
+ /** \brief Invoke manager->slFindAnn and wait for result.
+ */
+ optional<ndn::PrefixAnnouncement>
+ slFindAnnSync(const Name& name)
+ {
+ optional<optional<ndn::PrefixAnnouncement>> result;
+ manager->slFindAnn(name,
+ [&] (optional<ndn::PrefixAnnouncement> found) {
+ BOOST_CHECK(!result);
+ result = found;
+ });
+
+ getGlobalIoService().poll();
+ BOOST_CHECK(result);
+ return result.value_or(nullopt);
+ }
+
+ /** \brief Lookup a route with PREFIXANN origin.
+ */
+ Route*
+ findAnnRoute(const Name& name, uint64_t faceId)
+ {
+ Route routeQuery;
+ routeQuery.faceId = faceId;
+ routeQuery.origin = ndn::nfd::ROUTE_ORIGIN_PREFIXANN;
+ return rib.find(name, routeQuery);
+ }
+
+private:
+ /** \brief Prepare a trust schema and load as localhop_security.
+ *
+ * Test case may revert this operation with ribManager->disableLocalhop().
+ */
+ void
+ loadTrustSchema()
+ {
+ ConfigSection section;
+ section.put("rule.id", "PA");
+ section.put("rule.for", "data");
+ section.put("rule.checker.type", "customized");
+ section.put("rule.checker.sig-type", "rsa-sha256");
+ section.put("rule.checker.key-locator.type", "name");
+ section.put("rule.checker.key-locator.name", "/trusted");
+ section.put("rule.checker.key-locator.relation", "is-prefix-of");
+ section.put("trust-anchor.type", "base64");
+ section.put("trust-anchor.base64-string", getIdentityCertificateBase64("/trusted"));
+ manager->enableLocalhop(section, "trust-schema.section");
+ }
+
+public:
+ Rib rib;
+ unique_ptr<RibManager> manager;
+
+private:
+ ndn::util::DummyClientFace m_face;
+ ndn::nfd::Controller m_nfdController;
+ ndn::mgmt::Dispatcher m_dispatcher;
+ FibUpdater m_fibUpdater;
+
+ ndn::security::SigningInfo m_trustedSigner;
+ ndn::security::SigningInfo m_untrustedSigner;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestSlAnnounce, RibManagerSlAnnounceFixture)
+
+BOOST_AUTO_TEST_CASE(AnnounceUnconfigured)
+{
+ manager->disableLocalhop();
+ auto pa = makeTrustedAnn("/fMXN7UeB", 1_h);
+ BOOST_CHECK_EQUAL(slAnnounceSync(pa, 3275, 1_h), SlAnnounceResult::VALIDATION_FAILURE);
+
+ BOOST_CHECK(findAnnRoute("/fMXN7UeB", 3275) == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(AnnounceValidationError)
+{
+ auto pa = makeUntrustedAnn("/1nzAe0Y4", 1_h);
+ BOOST_CHECK_EQUAL(slAnnounceSync(pa, 2959, 1_h), SlAnnounceResult::VALIDATION_FAILURE);
+
+ BOOST_CHECK(findAnnRoute("/1nzAe0Y4", 2959) == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(AnnounceInsert_AnnLifetime)
+{
+ auto pa = makeTrustedAnn("/EHJYmJz9", 1_h);
+ BOOST_CHECK_EQUAL(slAnnounceSync(pa, 1641, 2_h), SlAnnounceResult::OK);
+
+ Route* route = findAnnRoute("/EHJYmJz9", 1641);
+ BOOST_REQUIRE(route != nullptr);
+ BOOST_CHECK_EQUAL(route->annExpires, time::steady_clock::now() + 1_h);
+ BOOST_CHECK_EQUAL(route->expires.value(), time::steady_clock::now() + 1_h);
+}
+
+BOOST_AUTO_TEST_CASE(AnnounceInsert_ArgLifetime)
+{
+ auto pa = makeTrustedAnn("/BU9Fec9E", 2_h);
+ BOOST_CHECK_EQUAL(slAnnounceSync(pa, 1282, 1_h), SlAnnounceResult::OK);
+
+ Route* route = findAnnRoute("/BU9Fec9E", 1282);
+ BOOST_REQUIRE(route != nullptr);
+ BOOST_CHECK_EQUAL(route->annExpires, time::steady_clock::now() + 2_h);
+ BOOST_CHECK_EQUAL(route->expires.value(), time::steady_clock::now() + 1_h);
+}
+
+BOOST_AUTO_TEST_CASE(AnnounceReplace)
+{
+ auto pa = makeTrustedAnn("/HsBFGvL3", 1_h);
+ BOOST_CHECK_EQUAL(slAnnounceSync(pa, 2813, 1_h), SlAnnounceResult::OK);
+
+ pa = makeTrustedAnn("/HsBFGvL3", 2_h);
+ BOOST_CHECK_EQUAL(slAnnounceSync(pa, 2813, 2_h), SlAnnounceResult::OK);
+
+ Route* route = findAnnRoute("/HsBFGvL3", 2813);
+ BOOST_REQUIRE(route != nullptr);
+ BOOST_CHECK_EQUAL(route->annExpires, time::steady_clock::now() + 2_h);
+ BOOST_CHECK_EQUAL(route->expires.value(), time::steady_clock::now() + 2_h);
+}
+
+BOOST_AUTO_TEST_CASE(AnnounceExpired)
+{
+ auto pa = makeTrustedAnn("/awrVv6V7", 1_h, std::make_pair(-3_h, -1_h));
+ BOOST_CHECK_EQUAL(slAnnounceSync(pa, 9087, 1_h), SlAnnounceResult::EXPIRED);
+
+ BOOST_CHECK(findAnnRoute("/awrVv6V7", 9087) == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(RenewNotFound)
+{
+ BOOST_CHECK_EQUAL(slRenewSync("IAYigN73", 1070, 1_h), SlAnnounceResult::NOT_FOUND);
+
+ BOOST_CHECK(findAnnRoute("/IAYigN73", 1070) == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(RenewProlong_ArgLifetime)
+{
+ auto pa = makeTrustedAnn("/P2IYFqtr", 4_h);
+ BOOST_CHECK_EQUAL(slAnnounceSync(pa, 4506, 2_h), SlAnnounceResult::OK);
+ advanceClocks(1_h); // Route has 1_h remaining lifetime
+
+ BOOST_CHECK_EQUAL(slRenewSync("/P2IYFqtr", 4506, 2_h), SlAnnounceResult::OK);
+
+ Route* route = findAnnRoute("/P2IYFqtr", 4506);
+ BOOST_REQUIRE(route != nullptr);
+ BOOST_CHECK_EQUAL(route->annExpires, time::steady_clock::now() + 3_h);
+ BOOST_CHECK_EQUAL(route->expires.value(), time::steady_clock::now() + 2_h); // set by slRenew
+}
+
+BOOST_AUTO_TEST_CASE(RenewProlong_AnnLifetime)
+{
+ auto pa = makeTrustedAnn("/be01Yiba", 4_h);
+ BOOST_CHECK_EQUAL(slAnnounceSync(pa, 1589, 2_h), SlAnnounceResult::OK);
+ advanceClocks(1_h); // Route has 1_h remaining lifetime
+
+ BOOST_CHECK_EQUAL(slRenewSync("/be01Yiba", 1589, 5_h), SlAnnounceResult::OK);
+
+ Route* route = findAnnRoute("/be01Yiba", 1589);
+ BOOST_REQUIRE(route != nullptr);
+ BOOST_CHECK_EQUAL(route->annExpires, time::steady_clock::now() + 3_h);
+ BOOST_CHECK_EQUAL(route->expires.value(), time::steady_clock::now() + 3_h); // capped by annExpires
+}
+
+BOOST_AUTO_TEST_CASE(RenewShorten)
+{
+ auto pa = makeTrustedAnn("/5XCHYCAd", 4_h);
+ BOOST_CHECK_EQUAL(slAnnounceSync(pa, 3851, 4_h), SlAnnounceResult::OK);
+ advanceClocks(1_h); // Route has 3_h remaining lifetime
+
+ BOOST_CHECK_EQUAL(slRenewSync("/5XCHYCAd", 3851, 1_h), SlAnnounceResult::OK);
+
+ Route* route = findAnnRoute("/5XCHYCAd", 3851);
+ BOOST_REQUIRE(route != nullptr);
+ BOOST_CHECK_EQUAL(route->annExpires, time::steady_clock::now() + 3_h);
+ BOOST_CHECK_EQUAL(route->expires.value(), time::steady_clock::now() + 1_h); // set by slRenew
+}
+
+BOOST_AUTO_TEST_CASE(RenewShorten_Zero)
+{
+ auto pa = makeTrustedAnn("/cdQ7KPNw", 4_h);
+ BOOST_CHECK_EQUAL(slAnnounceSync(pa, 8031, 4_h), SlAnnounceResult::OK);
+ advanceClocks(1_h); // Route has 3_h remaining lifetime
+
+ BOOST_CHECK_EQUAL(slRenewSync("/cdQ7KPNw", 8031, 0_s), SlAnnounceResult::EXPIRED);
+
+ BOOST_CHECK(findAnnRoute("/cdQ7KPNw", 8031) == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(FindExisting)
+{
+ auto pa = makeTrustedAnn("/JHugsjjr", 1_h);
+ BOOST_CHECK_EQUAL(slAnnounceSync(pa, 2363, 1_h), SlAnnounceResult::OK);
+
+ auto found = slFindAnnSync("/JHugsjjr");
+ BOOST_REQUIRE(found);
+ BOOST_CHECK_EQUAL(found->getAnnouncedName(), "/JHugsjjr");
+ BOOST_CHECK(found->getData());
+
+ auto found2 = slFindAnnSync("/JHugsjjr/StvXhKR5");
+ BOOST_CHECK(found == found2);
+}
+
+BOOST_AUTO_TEST_CASE(FindNew)
+{
+ Route route;
+ route.faceId = 1367;
+ route.origin = ndn::nfd::ROUTE_ORIGIN_APP;
+ rib.insert("/dLY1pRhR", route);
+
+ auto pa = slFindAnnSync("/dLY1pRhR/3qNK9Ngn");
+ BOOST_REQUIRE(pa);
+ BOOST_CHECK_EQUAL(pa->getAnnouncedName(), "/dLY1pRhR");
+}
+
+BOOST_AUTO_TEST_CASE(FindNone)
+{
+ auto pa = slFindAnnSync("/2YNeYuV2");
+ BOOST_CHECK(!pa);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSlAnnounce
+
+} // namespace tests
+} // namespace rib
+} // namespace nfd