util: Add BroadcastLink to DummyClientFace
Change-Id: I2bfe3156016a098b91ff375f2bb83b5e4351317c
Refs: #3913
diff --git a/src/util/dummy-client-face.cpp b/src/util/dummy-client-face.cpp
index 989b455..9f9383a 100644
--- a/src/util/dummy-client-face.cpp
+++ b/src/util/dummy-client-face.cpp
@@ -85,6 +85,16 @@
Signal<Transport, Block> onSendBlock;
};
+struct DummyClientFace::BroadcastLink
+{
+ std::vector<DummyClientFace*> faces;
+};
+
+DummyClientFace::AlreadyLinkedError::AlreadyLinkedError()
+ : Error("Face has already been linked to another face")
+{
+}
+
DummyClientFace::DummyClientFace(const Options& options/* = DummyClientFace::DEFAULT_OPTIONS*/)
: Face(make_shared<DummyClientFace::Transport>())
, m_internalKeyChain(new KeyChain)
@@ -118,6 +128,11 @@
this->construct(options);
}
+DummyClientFace::~DummyClientFace()
+{
+ unlink();
+}
+
void
DummyClientFace::construct(const Options& options)
{
@@ -159,6 +174,40 @@
this->enableRegistrationReply();
m_processEventsOverride = options.processEventsOverride;
+
+ enableBroadcastLink();
+}
+
+void
+DummyClientFace::enableBroadcastLink()
+{
+ this->onSendInterest.connect([this] (const Interest& interest) {
+ if (m_bcastLink != nullptr) {
+ for (auto otherFace : m_bcastLink->faces) {
+ if (otherFace != this) {
+ otherFace->receive(interest);
+ }
+ }
+ }
+ });
+ this->onSendData.connect([this] (const Data& data) {
+ if (m_bcastLink != nullptr) {
+ for (auto otherFace : m_bcastLink->faces) {
+ if (otherFace != this) {
+ otherFace->receive(data);
+ }
+ }
+ }
+ });
+ this->onSendNack.connect([this] (const lp::Nack& nack) {
+ if (m_bcastLink != nullptr) {
+ for (auto otherFace : m_bcastLink->faces) {
+ if (otherFace != this) {
+ otherFace->receive(nack);
+ }
+ }
+ }
+ });
}
void
@@ -241,6 +290,48 @@
}
void
+DummyClientFace::linkTo(DummyClientFace& other)
+{
+ if (m_bcastLink != nullptr && other.m_bcastLink != nullptr) {
+ if (m_bcastLink != other.m_bcastLink) {
+ // already on different links
+ BOOST_THROW_EXCEPTION(AlreadyLinkedError());
+ }
+ }
+ else if (m_bcastLink == nullptr && other.m_bcastLink != nullptr) {
+ m_bcastLink = other.m_bcastLink;
+ m_bcastLink->faces.push_back(this);
+ }
+ else if (m_bcastLink != nullptr && other.m_bcastLink == nullptr) {
+ other.m_bcastLink = m_bcastLink;
+ m_bcastLink->faces.push_back(&other);
+ }
+ else {
+ m_bcastLink = other.m_bcastLink = make_shared<BroadcastLink>();
+ m_bcastLink->faces.push_back(this);
+ m_bcastLink->faces.push_back(&other);
+ }
+}
+
+void
+DummyClientFace::unlink()
+{
+ if (m_bcastLink == nullptr) {
+ return;
+ }
+
+ auto it = std::find(m_bcastLink->faces.begin(), m_bcastLink->faces.end(), this);
+ BOOST_ASSERT(it != m_bcastLink->faces.end());
+ m_bcastLink->faces.erase(it);
+
+ if (m_bcastLink->faces.size() == 1) {
+ m_bcastLink->faces[0]->m_bcastLink = nullptr;
+ m_bcastLink->faces.clear();
+ }
+ m_bcastLink = nullptr;
+}
+
+void
DummyClientFace::doProcessEvents(time::milliseconds timeout, bool keepThread)
{
if (m_processEventsOverride != nullptr) {
diff --git a/src/util/dummy-client-face.hpp b/src/util/dummy-client-face.hpp
index 781a797..7d40f65 100644
--- a/src/util/dummy-client-face.hpp
+++ b/src/util/dummy-client-face.hpp
@@ -72,6 +72,12 @@
std::function<void(time::milliseconds)> processEventsOverride;
};
+ class AlreadyLinkedError : public Error
+ {
+ public:
+ AlreadyLinkedError();
+ };
+
/** \brief Create a dummy face with internal IO service
*/
explicit
@@ -92,6 +98,8 @@
DummyClientFace(boost::asio::io_service& ioService, KeyChain& keyChain,
const Options& options = Options());
+ ~DummyClientFace();
+
/** \brief cause the Face to receive an interest
*/
void
@@ -107,6 +115,16 @@
void
receive(const lp::Nack& nack);
+ /** \brief link another DummyClientFace through a broadcast media
+ */
+ void
+ linkTo(DummyClientFace& other);
+
+ /** \brief unlink the broadcast media if previously linked
+ */
+ void
+ unlink();
+
private:
class Transport;
@@ -114,6 +132,9 @@
construct(const Options& options);
void
+ enableBroadcastLink();
+
+ void
enablePacketLogging();
void
@@ -165,7 +186,9 @@
*/
Signal<DummyClientFace, lp::Nack> onSendNack;
-private:
+NDN_CXX_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+ struct BroadcastLink;
+ shared_ptr<BroadcastLink> m_bcastLink;
std::unique_ptr<KeyChain> m_internalKeyChain;
KeyChain& m_keyChain;
std::function<void(time::milliseconds)> m_processEventsOverride;
diff --git a/tests/unit-tests/util/dummy-client-face.t.cpp b/tests/unit-tests/util/dummy-client-face.t.cpp
index 809034d..8469d59 100644
--- a/tests/unit-tests/util/dummy-client-face.t.cpp
+++ b/tests/unit-tests/util/dummy-client-face.t.cpp
@@ -1,5 +1,5 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
+/*
* Copyright (c) 2013-2017 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
@@ -23,15 +23,16 @@
#include "boost-test.hpp"
#include "../identity-management-time-fixture.hpp"
+#include "make-interest-data.hpp"
namespace ndn {
namespace util {
namespace tests {
BOOST_AUTO_TEST_SUITE(Util)
-BOOST_AUTO_TEST_SUITE(TestDummyClientFace)
+BOOST_FIXTURE_TEST_SUITE(TestDummyClientFace, ndn::tests::IdentityManagementTimeFixture)
-BOOST_FIXTURE_TEST_CASE(ProcessEventsOverride, ndn::tests::IdentityManagementTimeFixture)
+BOOST_AUTO_TEST_CASE(ProcessEventsOverride)
{
bool isOverrideInvoked = false;
auto override = [&] (time::milliseconds timeout) {
@@ -44,6 +45,83 @@
BOOST_CHECK(isOverrideInvoked);
}
+BOOST_AUTO_TEST_CASE(BroadcastLink)
+{
+ DummyClientFace face1(io, m_keyChain, DummyClientFace::Options{true, true});
+ DummyClientFace face2(io, m_keyChain, DummyClientFace::Options{true, true});
+ face1.linkTo(face2);
+
+ int nFace1Interest = 0;
+ int nFace2Interest = 0;
+ face1.setInterestFilter("/face1",
+ [&] (const InterestFilter&, const Interest& interest) {
+ BOOST_CHECK_EQUAL(interest.getName().toUri(), "/face1/data");
+ nFace1Interest++;
+ face1.put(ndn::tests::makeNack(interest, lp::NackReason::NO_ROUTE));
+ }, nullptr, nullptr);
+ face2.setInterestFilter("/face2",
+ [&] (const InterestFilter&, const Interest& interest) {
+ BOOST_CHECK_EQUAL(interest.getName().toUri(), "/face2/data");
+ nFace2Interest++;
+ face2.put(*ndn::tests::makeData("/face2/data"));
+ return;
+ }, nullptr, nullptr);
+
+ advanceClocks(time::milliseconds(25), 4);
+
+ int nFace1Data = 0;
+ int nFace2Nack = 0;
+ face1.expressInterest(Interest("/face2/data"),
+ [&] (const Interest& i, const Data& d) {
+ BOOST_CHECK_EQUAL(d.getName().toUri(), "/face2/data");
+ nFace1Data++;
+ }, nullptr, nullptr);
+ face2.expressInterest(Interest("/face1/data"),
+ [&] (const Interest& i, const Data& d) {
+ BOOST_CHECK(false);
+ },
+ [&] (const Interest& i, const lp::Nack& n) {
+ BOOST_CHECK_EQUAL(n.getInterest().getName().toUri(), "/face1/data");
+ nFace2Nack++;
+ }, nullptr);
+
+ advanceClocks(time::milliseconds(10), 100);
+
+ BOOST_CHECK_EQUAL(nFace1Data, 1);
+ BOOST_CHECK_EQUAL(nFace2Nack, 1);
+ BOOST_CHECK_EQUAL(nFace1Interest, 1);
+ BOOST_CHECK_EQUAL(nFace2Interest, 1);
+}
+
+BOOST_AUTO_TEST_CASE(BroadcastLinkDestroy)
+{
+ DummyClientFace face1(io, m_keyChain, DummyClientFace::Options{true, true});
+ DummyClientFace face2(io, m_keyChain, DummyClientFace::Options{true, true});
+
+ face1.linkTo(face2);
+ face2.unlink();
+ BOOST_CHECK(face1.m_bcastLink == nullptr);
+
+ DummyClientFace face3(io, m_keyChain, DummyClientFace::Options{true, true});
+ face1.linkTo(face2);
+ face3.linkTo(face1);
+ face2.unlink();
+ BOOST_CHECK(face1.m_bcastLink != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(AlreadyLinkException)
+{
+ DummyClientFace face1(io, m_keyChain, DummyClientFace::Options{true, true});
+ DummyClientFace face2(io, m_keyChain, DummyClientFace::Options{true, true});
+ DummyClientFace face3(io, m_keyChain, DummyClientFace::Options{true, true});
+ DummyClientFace face4(io, m_keyChain, DummyClientFace::Options{true, true});
+
+ face1.linkTo(face2);
+ face3.linkTo(face4);
+
+ BOOST_CHECK_THROW(face2.linkTo(face3), DummyClientFace::AlreadyLinkedError);
+}
+
BOOST_AUTO_TEST_SUITE_END() // TestDummyClientFace
BOOST_AUTO_TEST_SUITE_END() // Util