380 lines
13 KiB
C++
380 lines
13 KiB
C++
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
|
|
/*
|
|
* Copyright (c) 2013-2023 Regents of the University of California.
|
|
*
|
|
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
|
|
*
|
|
* ndn-cxx library is free software: you can redistribute it and/or modify it under the
|
|
* terms of the GNU Lesser General Public License as published by the Free Software
|
|
* Foundation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* ndn-cxx library 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received copies of the GNU General Public License and GNU Lesser
|
|
* General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
* See AUTHORS.md for complete list of ndn-cxx authors and contributors.
|
|
*/
|
|
|
|
#define BOOST_TEST_MODULE ndn-cxx Integration (Face)
|
|
#include "tests/boost-test.hpp"
|
|
|
|
#include "ndn-cxx/face.hpp"
|
|
#include "ndn-cxx/transport/tcp-transport.hpp"
|
|
#include "ndn-cxx/transport/unix-transport.hpp"
|
|
#include "ndn-cxx/util/scheduler.hpp"
|
|
|
|
#include "tests/key-chain-fixture.hpp"
|
|
#include "tests/test-common.hpp"
|
|
|
|
#include <stdio.h>
|
|
#include <condition_variable>
|
|
#include <mutex>
|
|
#include <thread>
|
|
|
|
#include <boost/mpl/vector.hpp>
|
|
|
|
namespace ndn::tests {
|
|
|
|
static Name
|
|
makeVeryLongName(Name prefix = Name())
|
|
{
|
|
for (size_t i = 0; i <= MAX_NDN_PACKET_SIZE / 10; i++) {
|
|
prefix.append("0123456789");
|
|
}
|
|
return prefix;
|
|
}
|
|
|
|
static std::string
|
|
executeCommand(const std::string& cmd)
|
|
{
|
|
std::string output;
|
|
char buf[256];
|
|
FILE* pipe = popen(cmd.data(), "r");
|
|
BOOST_REQUIRE_MESSAGE(pipe != nullptr, "popen(" << cmd << ")");
|
|
while (fgets(buf, sizeof(buf), pipe) != nullptr) {
|
|
output += buf;
|
|
}
|
|
pclose(pipe);
|
|
return output;
|
|
}
|
|
|
|
template<typename TransportType>
|
|
class FaceFixture : public KeyChainFixture
|
|
{
|
|
protected:
|
|
FaceFixture()
|
|
: face(TransportType::create(""), m_keyChain)
|
|
, sched(face.getIoContext())
|
|
{
|
|
}
|
|
|
|
/** \brief Send an Interest from a secondary face
|
|
* \param delay scheduling delay before sending Interest
|
|
* \param interest the Interest
|
|
* \param[out] outcome the response, initially '?', 'D' for Data, 'N' for Nack, 'T' for timeout
|
|
* \return scheduled event id
|
|
*/
|
|
scheduler::EventId
|
|
sendInterest(time::nanoseconds delay, const Interest& interest, char& outcome)
|
|
{
|
|
if (face2 == nullptr) {
|
|
face2 = make_unique<Face>(TransportType::create(""), face.getIoContext(), m_keyChain);
|
|
}
|
|
|
|
outcome = '?';
|
|
return sched.schedule(delay, [this, interest, &outcome] {
|
|
face2->expressInterest(interest,
|
|
[&] (const Interest&, const Data&) { outcome = 'D'; },
|
|
[&] (const Interest&, const lp::Nack&) { outcome = 'N'; },
|
|
[&] (const Interest&) { outcome = 'T'; });
|
|
});
|
|
}
|
|
|
|
scheduler::EventId
|
|
sendInterest(time::nanoseconds delay, const Interest& interest)
|
|
{
|
|
static char ignoredOutcome;
|
|
return sendInterest(delay, interest, ignoredOutcome);
|
|
}
|
|
|
|
/** \brief Stop io_context after a delay
|
|
* \return scheduled event id
|
|
*/
|
|
scheduler::EventId
|
|
terminateAfter(time::nanoseconds delay)
|
|
{
|
|
return sched.schedule(delay, [this] { face.getIoContext().stop(); });
|
|
}
|
|
|
|
protected:
|
|
Face face;
|
|
unique_ptr<Face> face2;
|
|
Scheduler sched;
|
|
};
|
|
|
|
using Transports = boost::mpl::vector<UnixTransport, TcpTransport>;
|
|
|
|
BOOST_AUTO_TEST_SUITE(Consumer)
|
|
|
|
BOOST_FIXTURE_TEST_CASE_TEMPLATE(ExpressInterestData, TransportType, Transports, FaceFixture<TransportType>)
|
|
{
|
|
int nData = 0;
|
|
this->face.expressInterest(*makeInterest("/localhost", true),
|
|
[&] (const Interest&, const Data&) { ++nData; },
|
|
[] (const Interest&, const lp::Nack&) { BOOST_ERROR("unexpected Nack"); },
|
|
[] (const Interest&) { BOOST_ERROR("unexpected timeout"); });
|
|
|
|
this->face.processEvents();
|
|
BOOST_CHECK_EQUAL(nData, 1);
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE_TEMPLATE(ExpressInterestNack, TransportType, Transports, FaceFixture<TransportType>)
|
|
{
|
|
int nNacks = 0;
|
|
this->face.expressInterest(*makeInterest("/localhost/non-existent-should-nack"),
|
|
[] (const Interest&, const Data&) { BOOST_ERROR("unexpected Data"); },
|
|
[&] (const Interest&, const lp::Nack&) { ++nNacks; },
|
|
[] (const Interest&) { BOOST_ERROR("unexpected timeout"); });
|
|
|
|
this->face.processEvents();
|
|
BOOST_CHECK_EQUAL(nNacks, 1);
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE_TEMPLATE(ExpressInterestTimeout, TransportType, Transports, FaceFixture<TransportType>)
|
|
{
|
|
// add route toward null face so Interest would timeout instead of getting Nacked
|
|
executeCommand("nfdc route add /localhost/non-existent-should-timeout null://");
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(200)); // wait for FIB update to take effect
|
|
|
|
int nTimeouts = 0;
|
|
this->face.expressInterest(*makeInterest("/localhost/non-existent-should-timeout", false, 1_s),
|
|
[] (const Interest&, const Data&) { BOOST_ERROR("unexpected Data"); },
|
|
[] (const Interest&, const lp::Nack&) { BOOST_ERROR("unexpected Nack"); },
|
|
[&] (const Interest&) { ++nTimeouts; });
|
|
|
|
this->face.processEvents();
|
|
BOOST_CHECK_EQUAL(nTimeouts, 1);
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE_TEMPLATE(OversizedInterest, TransportType, Transports, FaceFixture<TransportType>)
|
|
{
|
|
BOOST_CHECK_THROW(do {
|
|
this->face.expressInterest(*makeInterest(makeVeryLongName()), nullptr, nullptr, nullptr);
|
|
this->face.processEvents();
|
|
} while (false), Face::OversizedPacketError);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END() // Consumer
|
|
|
|
BOOST_AUTO_TEST_SUITE(Producer)
|
|
|
|
BOOST_FIXTURE_TEST_CASE_TEMPLATE(RegisterUnregisterPrefix, TransportType, Transports, FaceFixture<TransportType>)
|
|
{
|
|
this->terminateAfter(4_s);
|
|
|
|
int nRegSuccess = 0, nUnregSuccess = 0;
|
|
auto handle = this->face.registerPrefix("/Hello/World",
|
|
[&] (const Name&) { ++nRegSuccess; },
|
|
[] (const Name&, const auto& msg) { BOOST_ERROR("unexpected register prefix failure: " << msg); });
|
|
|
|
this->sched.schedule(1_s, [&nRegSuccess] {
|
|
BOOST_CHECK_EQUAL(nRegSuccess, 1);
|
|
std::string output = executeCommand("nfdc route list | grep /Hello/World");
|
|
BOOST_CHECK(!output.empty());
|
|
});
|
|
|
|
this->sched.schedule(2_s, [&] {
|
|
handle.unregister(
|
|
[&] { ++nUnregSuccess; },
|
|
[] (const auto& msg) { BOOST_ERROR("unexpected unregister prefix failure: " << msg); });
|
|
});
|
|
|
|
this->sched.schedule(3_s, [&nUnregSuccess] {
|
|
BOOST_CHECK_EQUAL(nUnregSuccess, 1);
|
|
|
|
// Boost.Test would fail if a child process exits with non-zero. http://stackoverflow.com/q/5325202
|
|
std::string output = executeCommand("nfdc route list | grep /Hello/World || true");
|
|
BOOST_CHECK(output.empty());
|
|
});
|
|
|
|
this->face.processEvents();
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE_TEMPLATE(RegularFilter, TransportType, Transports, FaceFixture<TransportType>)
|
|
{
|
|
this->terminateAfter(2_s);
|
|
|
|
int nInterests1 = 0, nRegSuccess1 = 0, nRegSuccess2 = 0;
|
|
this->face.setInterestFilter("/Hello/World",
|
|
[&] (const InterestFilter&, const Interest&) { ++nInterests1; },
|
|
[&] (const Name&) { ++nRegSuccess1; },
|
|
[] (const Name&, const auto& msg) { BOOST_ERROR("unexpected register prefix failure: " << msg); });
|
|
this->face.setInterestFilter("/Los/Angeles/Lakers",
|
|
[&] (const InterestFilter&, const Interest&) { BOOST_ERROR("unexpected Interest"); },
|
|
[&] (const Name&) { ++nRegSuccess2; },
|
|
[] (const Name&, const auto& msg) { BOOST_ERROR("unexpected register prefix failure: " << msg); });
|
|
|
|
this->sched.schedule(500_ms, [] {
|
|
std::string output = executeCommand("nfdc route list | grep /Hello/World");
|
|
BOOST_CHECK(!output.empty());
|
|
});
|
|
|
|
char interestOutcome;
|
|
this->sendInterest(1_s, *makeInterest("/Hello/World/regular", false, 50_ms), interestOutcome);
|
|
|
|
this->face.processEvents();
|
|
BOOST_CHECK_EQUAL(interestOutcome, 'T');
|
|
BOOST_CHECK_EQUAL(nInterests1, 1);
|
|
BOOST_CHECK_EQUAL(nRegSuccess1, 1);
|
|
BOOST_CHECK_EQUAL(nRegSuccess2, 1);
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE_TEMPLATE(RegexFilter, TransportType, Transports, FaceFixture<TransportType>)
|
|
{
|
|
this->terminateAfter(2_s);
|
|
|
|
int nRegSuccess = 0;
|
|
std::set<Name> receivedInterests;
|
|
this->face.setInterestFilter(InterestFilter("/Hello/World", "<><b><c>?"),
|
|
[&] (auto&&, const auto& interest) { receivedInterests.insert(interest.getName()); },
|
|
[&] (auto&&) { ++nRegSuccess; },
|
|
[] (auto&&, const auto& msg) { BOOST_ERROR("unexpected register prefix failure: " << msg); });
|
|
|
|
this->sched.schedule(700_ms, [] {
|
|
std::string output = executeCommand("nfdc route list | grep /Hello/World");
|
|
BOOST_CHECK(!output.empty());
|
|
});
|
|
|
|
this->sendInterest(200_ms, *makeInterest("/Hello/World/a", false, 50_ms));
|
|
this->sendInterest(300_ms, *makeInterest("/Hello/World/a/b", false, 50_ms));
|
|
this->sendInterest(400_ms, *makeInterest("/Hello/World/a/b/c", false, 50_ms));
|
|
this->sendInterest(500_ms, *makeInterest("/Hello/World/a/b/d", false, 50_ms));
|
|
|
|
this->face.processEvents();
|
|
BOOST_CHECK_EQUAL(nRegSuccess, 1);
|
|
std::set<Name> expectedInterests{"/Hello/World/a/b", "/Hello/World/a/b/c"};
|
|
BOOST_TEST(receivedInterests == expectedInterests, boost::test_tools::per_element());
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE_TEMPLATE(RegexFilterNoRegister, TransportType, Transports, FaceFixture<TransportType>)
|
|
{
|
|
this->terminateAfter(2_s);
|
|
|
|
// no Interest shall arrive because prefix isn't registered in forwarder
|
|
this->face.setInterestFilter(InterestFilter("/Hello/World", "<><b><c>?"),
|
|
[&] (const InterestFilter&, const Interest&) { BOOST_ERROR("unexpected Interest"); });
|
|
|
|
this->sched.schedule(700_ms, [] {
|
|
// Boost.Test would fail if a child process exits with non-zero. http://stackoverflow.com/q/5325202
|
|
std::string output = executeCommand("nfdc route list | grep /Hello/World || true");
|
|
BOOST_CHECK(output.empty());
|
|
});
|
|
|
|
this->sendInterest(200_ms, *makeInterest("/Hello/World/a", false, 50_ms));
|
|
this->sendInterest(300_ms, *makeInterest("/Hello/World/a/b", false, 50_ms));
|
|
this->sendInterest(400_ms, *makeInterest("/Hello/World/a/b/c", false, 50_ms));
|
|
this->sendInterest(500_ms, *makeInterest("/Hello/World/a/b/d", false, 50_ms));
|
|
|
|
this->face.processEvents();
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE_TEMPLATE(PutDataNack, TransportType, Transports, FaceFixture<TransportType>)
|
|
{
|
|
this->terminateAfter(2_s);
|
|
|
|
this->face.setInterestFilter("/Hello/World",
|
|
[&] (const InterestFilter&, const Interest& interest) {
|
|
if (interest.getName().at(2) == name::Component("nack")) {
|
|
this->face.put(makeNack(interest, lp::NackReason::NO_ROUTE));
|
|
}
|
|
else {
|
|
this->face.put(*makeData(interest.getName()));
|
|
}
|
|
},
|
|
nullptr,
|
|
[] (const Name&, const auto& msg) { BOOST_ERROR("unexpected register prefix failure: " << msg); });
|
|
|
|
char outcome1, outcome2;
|
|
this->sendInterest(700_ms, *makeInterest("/Hello/World/data", false, 50_ms), outcome1);
|
|
this->sendInterest(800_ms, *makeInterest("/Hello/World/nack", false, 50_ms), outcome2);
|
|
|
|
this->face.processEvents();
|
|
BOOST_CHECK_EQUAL(outcome1, 'D');
|
|
BOOST_CHECK_EQUAL(outcome2, 'N');
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE_TEMPLATE(OversizedData, TransportType, Transports, FaceFixture<TransportType>)
|
|
{
|
|
this->terminateAfter(2_s);
|
|
|
|
this->face.setInterestFilter("/Hello/World",
|
|
[&] (const InterestFilter&, const Interest& interest) {
|
|
this->face.put(*makeData(makeVeryLongName(interest.getName())));
|
|
},
|
|
nullptr,
|
|
[] (const Name&, const auto& msg) { BOOST_ERROR("unexpected register prefix failure: " << msg); });
|
|
|
|
this->sendInterest(1_s, *makeInterest("/Hello/World/oversized", true, 50_ms));
|
|
|
|
BOOST_CHECK_THROW(this->face.processEvents(), Face::OversizedPacketError);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END() // Producer
|
|
|
|
BOOST_FIXTURE_TEST_SUITE(IoRoutine, FaceFixture<UnixTransport>)
|
|
|
|
BOOST_AUTO_TEST_CASE(ShutdownWhileSendInProgress) // Bug #3136
|
|
{
|
|
this->face.expressInterest(*makeInterest("/Hello/World"), nullptr, nullptr, nullptr);
|
|
this->face.processEvents(1_s);
|
|
|
|
this->face.expressInterest(*makeInterest("/Bye/World/1"), nullptr, nullptr, nullptr);
|
|
this->face.expressInterest(*makeInterest("/Bye/World/2"), nullptr, nullptr, nullptr);
|
|
this->face.expressInterest(*makeInterest("/Bye/World/3"), nullptr, nullptr, nullptr);
|
|
this->face.shutdown();
|
|
|
|
this->face.processEvents(1_s); // should not segfault
|
|
BOOST_CHECK(true);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(LargeDelayBetweenFaceConstructorAndProcessEvents) // Bug #2742
|
|
{
|
|
std::this_thread::sleep_for(std::chrono::seconds(5)); // simulate setup workload
|
|
this->face.processEvents(1_s); // should not throw
|
|
BOOST_CHECK(true);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(ProcessEventsBlocksForeverWhenNothingScheduled) // Bug #3957
|
|
{
|
|
std::mutex m;
|
|
std::condition_variable cv;
|
|
bool processEventsFinished = false;
|
|
|
|
std::thread faceThread([&] {
|
|
this->face.processEvents();
|
|
|
|
processEventsFinished = true;
|
|
std::lock_guard<std::mutex> lk(m);
|
|
cv.notify_one();
|
|
});
|
|
|
|
{
|
|
std::unique_lock<std::mutex> lk(m);
|
|
cv.wait_for(lk, std::chrono::seconds(5), [&] { return processEventsFinished; });
|
|
}
|
|
|
|
BOOST_CHECK_EQUAL(processEventsFinished, true);
|
|
if (!processEventsFinished) {
|
|
this->face.shutdown();
|
|
}
|
|
faceThread.join();
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END() // IoRoutine
|
|
|
|
} // namespace ndn::tests
|