From 3ecec50221ce10e54f8cd699ef246f7d64a90fef Mon Sep 17 00:00:00 2001 From: Alexander Afanasyev Date: Wed, 16 Apr 2014 13:42:44 -0700 Subject: [PATCH] rib: Integrating NRD code into NFD codebase Change-Id: I3e548f974255f62a4680cfc6c12be3bb7a3db4d2 Refs: #1486 --- AUTHORS.md | 5 + README-dev.md | 3 +- {rib/src => core}/face-monitor.cpp | 48 ++-- core/face-monitor.hpp | 105 ++++++++ docs/conf.py | 1 + docs/manpages.rst | 1 + docs/manpages/nrd.rst | 67 +++++ rib/nrd.conf.sample => nrd.conf.sample | 0 rib/.waf-tools/flags.py | 52 ---- rib/INSTALL.md | 16 -- rib/README.md | 67 ----- rib/main.cpp | 257 +++++++++++++++++++ rib/rib-manager.cpp | 341 +++++++++++++++++++++++++ rib/rib-manager.hpp | 157 ++++++++++++ rib/{src => }/rib.cpp | 33 ++- rib/rib.hpp | 108 ++++++++ rib/src/common.hpp | 45 ---- rib/src/face-monitor.hpp | 72 ------ rib/src/main.cpp | 100 -------- rib/src/nrd-config.cpp | 116 --------- rib/src/nrd-config.hpp | 61 ----- rib/src/nrd.cpp | 304 ---------------------- rib/src/nrd.hpp | 118 --------- rib/src/rib.hpp | 84 ------ rib/tests/main.cpp | 10 - rib/tests/test-rib.cpp | 67 ----- rib/waf | Bin 91336 -> 0 bytes rib/wscript | 71 ----- tests/rib/rib.cpp | 86 +++++++ tests/wscript | 9 + wscript | 18 ++ 31 files changed, 1215 insertions(+), 1207 deletions(-) rename {rib/src => core}/face-monitor.cpp (67%) create mode 100644 core/face-monitor.hpp create mode 100644 docs/manpages/nrd.rst rename rib/nrd.conf.sample => nrd.conf.sample (100%) delete mode 100644 rib/.waf-tools/flags.py delete mode 100644 rib/INSTALL.md delete mode 100644 rib/README.md create mode 100644 rib/main.cpp create mode 100644 rib/rib-manager.cpp create mode 100644 rib/rib-manager.hpp rename rib/{src => }/rib.cpp (58%) create mode 100644 rib/rib.hpp delete mode 100644 rib/src/common.hpp delete mode 100644 rib/src/face-monitor.hpp delete mode 100644 rib/src/main.cpp delete mode 100644 rib/src/nrd-config.cpp delete mode 100644 rib/src/nrd-config.hpp delete mode 100644 rib/src/nrd.cpp delete mode 100644 rib/src/nrd.hpp delete mode 100644 rib/src/rib.hpp delete mode 100644 rib/tests/main.cpp delete mode 100644 rib/tests/test-rib.cpp delete mode 100755 rib/waf delete mode 100644 rib/wscript create mode 100644 tests/rib/rib.cpp diff --git a/AUTHORS.md b/AUTHORS.md index cad7c8b2..f71d42d7 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -56,3 +56,8 @@ volunteers. * Haowei Yuan * Hila Ben Abraham * Patrick Crowley + +* The University of Memphis + + * Syed Obaid Amin + * Lan Wang diff --git a/README-dev.md b/README-dev.md index b4cba899..d9cae4af 100644 --- a/README-dev.md +++ b/README-dev.md @@ -10,7 +10,8 @@ Include the following header into all `.hpp` and `.cpp` files: * Colorado State University, * University Pierre & Marie Curie, Sorbonne University, * Washington University in St. Louis, - * Beijing Institute of Technology + * 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. diff --git a/rib/src/face-monitor.cpp b/core/face-monitor.cpp similarity index 67% rename from rib/src/face-monitor.cpp rename to core/face-monitor.cpp index f4345238..658d4b9c 100644 --- a/rib/src/face-monitor.cpp +++ b/core/face-monitor.cpp @@ -1,19 +1,38 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /** - * Copyright (C) 2014 Regents of the University of California. - * See COPYING for copyright and distribution information. - */ + * Copyright (c) 2014 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 . + **/ -#include "common.hpp" #include "face-monitor.hpp" -#include +#include "core/logger.hpp" +#include -namespace ndn { +namespace nfd { -using namespace nfd; +NFD_LOG_INIT("FaceMonitor"); -FaceMonitor::FaceMonitor(Face& face) +FaceMonitor::FaceMonitor(ndn::Face& face) : m_face(face) , m_isStopped(true) { @@ -53,8 +72,7 @@ FaceMonitor::startNotifications() .setInterestLifetime(time::seconds(60)) ; - //todo: add logging support. - std::cout << "startNotification: Interest Sent: " << interest << std::endl; + NFD_LOG_DEBUG("startNotification: Interest Sent: " << interest); m_lastInterestId = m_face.expressInterest(interest, bind(&FaceMonitor::onNotification, this, _2), @@ -103,8 +121,7 @@ FaceMonitor::onTimeout() .setInterestLifetime(time::seconds(60)) ; - //todo: add logging support. - std::cout << "In onTimeout, sending interest: " << newInterest << std::endl; + NFD_LOG_DEBUG("In onTimeout, sending interest: " << newInterest); m_lastInterestId = m_face.expressInterest(newInterest, bind(&FaceMonitor::onNotification, this, _2), @@ -138,12 +155,11 @@ FaceMonitor::onNotification(const Data& data) Interest interest(nextNotification); interest.setInterestLifetime(time::seconds(60)); - //todo: add logging support. - std::cout << "onNotification: Interest sent: " << interest << std::endl; + NFD_LOG_DEBUG("onNotification: Interest sent: " << interest); m_lastInterestId = m_face.expressInterest(interest, bind(&FaceMonitor::onNotification, this, _2), bind(&FaceMonitor::onTimeout, this)); } -}//namespace ndn +} // namespace nfd diff --git a/core/face-monitor.hpp b/core/face-monitor.hpp new file mode 100644 index 00000000..6ce65955 --- /dev/null +++ b/core/face-monitor.hpp @@ -0,0 +1,105 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/** + * Copyright (c) 2014 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 . + **/ + +#ifndef NFD_CORE_FACE_MONITOR_HPP +#define NFD_CORE_FACE_MONITOR_HPP + +#include "common.hpp" + +#include + +namespace ndn { +class Face; +struct PendingInterestId; +} + +namespace nfd { + +using ndn::nfd::FaceEventNotification; + +/** + * \brief Helper class to subscribe to face notification events + * + * \todo Write test cases + */ +class FaceMonitor : noncopyable +{ +public: + typedef function NotificationCallback; + typedef function TimeoutCallback; + + typedef std::vector NotificationCallbacks; + typedef std::vector TimeoutCallbacks; + + explicit + FaceMonitor(ndn::Face& face); + + ~FaceMonitor(); + + /** \brief Stops all notifications. This method doesn't remove registered callbacks. + */ + void + stopNotifications(); + + /** \brief Resumes notifications for added subscribers. + */ + void + startNotifications(); + + /** \brief Removes all notification subscribers. + */ + void + removeAllSubscribers(); + + /** \brief Adds a notification subscriber. This method doesn't return on timeouts. + */ + void + addSubscriber(const NotificationCallback& notificationCallback); + + /** \brief Adds a notification subscriber. + */ + void + addSubscriber(const NotificationCallback& notificationCallback, + const TimeoutCallback& timeoutCallback); + +private: + void + onTimeout(); + + void + onNotification(const Data& data); + +private: + ndn::Face& m_face; + uint64_t m_lastSequence; + bool m_isStopped; + NotificationCallbacks m_notificationCallbacks; + TimeoutCallbacks m_timeoutCallbacks; + const ndn::PendingInterestId* m_lastInterestId; +}; + +} // namespace nfd + +#endif // NFD_CORE_FACE_MONITOR_HPP diff --git a/docs/conf.py b/docs/conf.py index 11d8a52d..647cca40 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -230,6 +230,7 @@ latex_documents = [ # (source start file, name, description, authors, manual section). man_pages = [ ('manpages/nfd', 'nfd', u'Named Data Networking Forwarding Daemon', None, 1), + ('manpages/nrd', 'nrd', u'NFD RIB Daemon', None, 1), ('manpages/ndn-autoconfig-server', 'ndn-autoconfig-server', u'NFD Auto-configuration Server', None, 1), ('manpages/ndn-autoconfig', 'ndn-autoconfig', diff --git a/docs/manpages.rst b/docs/manpages.rst index da28853c..ff5630ad 100644 --- a/docs/manpages.rst +++ b/docs/manpages.rst @@ -3,5 +3,6 @@ NFD Manpages .. toctree:: manpages/nfd + manpages/nrd manpages/ndn-autoconfig manpages/ndn-autoconfig-server diff --git a/docs/manpages/nrd.rst b/docs/manpages/nrd.rst new file mode 100644 index 00000000..a8255759 --- /dev/null +++ b/docs/manpages/nrd.rst @@ -0,0 +1,67 @@ +NRD (NFD RIB Daemon) +======================== + +NRD is a complementary daemon designed to work in parallel with NFD +and to provide services for Routing Information Base management and +constructing NFD's FIB: + +* prefix registration from applications; +* manual prefix registration; +* prefix registrations from multiple dynamic routing protocols. + +Running and Configuring NRD +--------------------------- + +NRD's runtime settings may be modified via configuration file. After +installation, a working sample configuration is provided at +``SYSCONFDIR/ndn/nrd.conf.sample``. At startup, NRD will attempt to read +the default configuration file from following location: +``SYSCONFDIR/ndn/nrd.conf``. + +You may also specify an alternative configuration file location by +running NRD with: + +:: + + nrd --config + +Currently, nrd.conf contains only a security section, which is well +documented in the sample configuration file. + +Starting experiments +-------------------- + +1. Build, install and setup NFD by following the directions provided in + README.md in NFD root folder. + +2. Create certificates by following the instructions in NFD's README.md + +3. Build and install NRD by following the directions in INSTALL.md + +4. Create nrd.conf file. You can simply copy the provided sample file in + ``SYSCONFDIR/ndn/nrd.conf``. + +5. Setup the trust-anchors in nrd.conf. Ideally, the prefix registration + Interest should be signed by application/user key, and this + application/user key should be used as trust-anchor. However, + currently the default certificate is used to sign the registration + Interests. Therefore, for initial testing the default certificate + should be set as the trust-anchor. For doing so, the default + certificate should be copied in the same folder where the nrd.conf + is. Secondly, the trust-anchor should point to the copied certificate + in nrd.conf. To know the id of default certificate you can use: + + ~$ ndnsec-get-default + +6. Start NFD (assuming the NFD's conf file exist in + ``SYSCONFDIR/ndn/nfd.conf``): + + ~$ nfd + +7. Start NRD (assuming the conf file exist in + ``SYSCONFDIR/ndn/nrd.conf``): + + ~$ nrd + +8. Try to connect any application to NFD. For example, producer + application that is provided ndn-cpp-dev examples. diff --git a/rib/nrd.conf.sample b/nrd.conf.sample similarity index 100% rename from rib/nrd.conf.sample rename to nrd.conf.sample diff --git a/rib/.waf-tools/flags.py b/rib/.waf-tools/flags.py deleted file mode 100644 index dea80b8e..00000000 --- a/rib/.waf-tools/flags.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*- - -from waflib import Logs, Configure - -def configure(conf): - areCustomCxxflagsPresent = (len(conf.env.CXXFLAGS) > 0) - if conf.options.debug: - conf.define('_DEBUG', 1) - defaultFlags = ['-O0', '-g3', - '-Werror', - '-Wall', - '-fcolor-diagnostics', # only clang supports - - # # to disable known warnings - # '-Wno-unused-variable', # cryptopp - # '-Wno-unused-function', - # '-Wno-deprecated-declarations', - ] - - if areCustomCxxflagsPresent: - missingFlags = [x for x in defaultFlags if x not in conf.env.CXXFLAGS] - if len(missingFlags) > 0: - Logs.warn("Selected debug mode, but CXXFLAGS is set to a custom value '%s'" - % " ".join(conf.env.CXXFLAGS)) - Logs.warn("Default flags '%s' are not activated" % " ".join(missingFlags)) - else: - conf.add_supported_cxxflags(cxxflags=defaultFlags) - else: - defaultFlags = ['-O2', '-g', '-Wall', - - # # to disable known warnings - # '-Wno-unused-variable', # cryptopp - # '-Wno-unused-function', - # '-Wno-deprecated-declarations', - ] - if not areCustomCxxflagsPresent: - conf.add_supported_cxxflags(cxxflags=defaultFlags) - -@Configure.conf -def add_supported_cxxflags(self, cxxflags): - """ - Check which cxxflags are supported by compiler and add them to env.CXXFLAGS variable - """ - self.start_msg('Checking allowed flags for c++ compiler') - - supportedFlags = [] - for flag in cxxflags: - if self.check_cxx (cxxflags=[flag], mandatory=False): - supportedFlags += [flag] - - self.end_msg (' '.join (supportedFlags)) - self.env.CXXFLAGS += supportedFlags diff --git a/rib/INSTALL.md b/rib/INSTALL.md deleted file mode 100644 index 868a8d10..00000000 --- a/rib/INSTALL.md +++ /dev/null @@ -1,16 +0,0 @@ -## Prerequisites - -* Boost libraries >= 1.48 -* [NDN-CPP-dev library](https://github.com/named-data/ndn-cpp-dev) -* `pkg-config` - -## Build - -The following commands should be used to build NRD: - - ./waf configure - ./waf - -If NRD needs to be installed - - sudo ./waf install diff --git a/rib/README.md b/rib/README.md deleted file mode 100644 index fbbe11ad..00000000 --- a/rib/README.md +++ /dev/null @@ -1,67 +0,0 @@ -NRD (NDN Routing Daemon) -======================== -NRD works in parallel with NFD and provides services for prefix registration. - -For installation details please see INSTALL.md. - -## Default Paths - -This README uses `SYSCONFDIR` when referring to the default locations of -various configuration files. By default, `SYSCONFDIR` is set to -`/usr/local/etc`. If you override just `PREFIX`, then `SYSCONFDIR` will -default to `PREFIX/etc`. - -You may override `SYSCONFDIR` and/or `PREFIX` by specifying their -corresponding options during configuration: - - ./waf configure --prefix --sysconfdir - -## Running and Configuring NRD - -NRD's runtime settings may be modified via configuration file. After -installation, a working sample configuration is provided at -`SYSCONFDIR/ndn/nrd.conf.sample`. At startup, NRD will attempt to -read the default configuration file from following location: -`SYSCONFDIR/ndn/nrd.conf`. - -You may also specify an alternative configuration file location -by running NRD with: - - nrd --config - -Currently, nrd.conf contains only a security section, which is well documented -in the sample configuration file. - -## Starting experiments - -1. Build, install and setup NFD by following the directions provided in -README.md in NFD root folder. - -2. Create certificates by following the instructions in NFD's README.md - -3. Build and install NRD by following the directions in INSTALL.md - -4. Create nrd.conf file. You can simply copy the provided sample file in -`SYSCONFDIR/ndn/nrd.conf`. - -5. Setup the trust-anchors in nrd.conf. Ideally, the prefix registration -Interest should be signed by application/user key, and this application/user -key should be used as trust-anchor. However, currently the default certificate -is used to sign the registration Interests. Therefore, for initial testing the -default certificate should be set as the trust-anchor. For doing so, the default -certificate should be copied in the same folder where the nrd.conf is. -Secondly, the trust-anchor should point to the copied certificate in nrd.conf. -To know the id of default certificate you can use: - - ~$ ndnsec-get-default - -6. Start NFD (assuming the NFD's conf file exist in `SYSCONFDIR/ndn/nfd.conf`): - - ~$ nfd - -7. Start NRD (assuming the conf file exist in `SYSCONFDIR/ndn/nrd.conf`): - - ~$ nrd - -8. Try to connect any application to NFD. For example, producer application that is provided -ndn-cpp-dev examples. diff --git a/rib/main.cpp b/rib/main.cpp new file mode 100644 index 00000000..7eddfdda --- /dev/null +++ b/rib/main.cpp @@ -0,0 +1,257 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/** + * Copyright (c) 2014 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 . + **/ + +#include + +#include "common.hpp" +#include "rib-manager.hpp" +#include "core/config-file.hpp" +#include "core/logger.hpp" + +namespace nfd { +namespace rib { + +NFD_LOG_INIT("nrd.Main"); + +struct ProgramOptions +{ + bool showUsage; + bool showModules; + std::string config; +}; + +class Nrd : noncopyable +{ +public: + void + initialize(const std::string& configFile) + { + initializeLogging(configFile); + + m_ribManager = make_shared(); + + ConfigFile config; + m_ribManager->setConfigFile(config); + config.addSectionHandler("log", bind(std::plus(), 0, 0)); // no-op + + // parse config file + config.parse(configFile, true); + config.parse(configFile, false); + + m_ribManager->registerWithNfd(); + m_ribManager->enableLocalControlHeader(); + } + + void + initializeLogging(const std::string& configFile) + { + ConfigFile config; + LoggerFactory::getInstance().setConfigFile(config); + + for (size_t i = 0; i < N_SUPPORTED_CONFIG_SECTIONS; ++i) + { + if (SUPPORTED_CONFIG_SECTIONS[i] != "log") + { + config.addSectionHandler(SUPPORTED_CONFIG_SECTIONS[i], + bind(std::plus(), 0, 0)); // no-op. + } + } + + config.parse(configFile, true); + config.parse(configFile, false); + } + + boost::asio::io_service& + getIoService() + { + return m_ribManager->getIoService(); + } + + static void + printUsage(std::ostream& os, const std::string& programName) + { + os << "Usage: \n" + << " " << programName << " [options]\n" + << "\n" + << "Run NRD daemon\n" + << "\n" + << "Options:\n" + << " [--help] - print this help message\n" + << " [--config /path/to/nrd.conf] - path to configuration file " + << "(default: " << DEFAULT_CONFIG_FILE << ")\n" + ; + } + + static void + printModules(std::ostream& os) + { + using namespace std; + + os << "Available logging modules: \n"; + + list modules(LoggerFactory::getInstance().getModules()); + for (list::const_iterator i = modules.begin(); i != modules.end(); ++i) + { + os << *i << "\n"; + } + } + + static bool + parseCommandLine(int argc, char** argv, ProgramOptions& options) + { + options.showUsage = false; + options.config = DEFAULT_CONFIG_FILE; + + while (true) { + int optionIndex = 0; + static ::option longOptions[] = { + { "help" , no_argument , 0, 0 }, + { "config" , required_argument, 0, 0 }, + { 0 , 0 , 0, 0 } + }; + int c = getopt_long_only(argc, argv, "", longOptions, &optionIndex); + if (c == -1) + break; + + switch (c) { + case 0: + switch (optionIndex) { + case 0: // help + options.showUsage = true; + break; + case 1: // config + options.config = ::optarg; + break; + default: + return false; + } + break; + } + } + return true; + } + + + void + terminate(const boost::system::error_code& error, + int signalNo, + boost::asio::signal_set& signalSet) + { + if (error) + return; + + if (signalNo == SIGINT || + signalNo == SIGTERM) + { + getIoService().stop(); + std::cout << "Caught signal '" << strsignal(signalNo) << "', exiting..." << std::endl; + } + else + { + /// \todo May be try to reload config file (at least security section) + signalSet.async_wait(bind(&Nrd::terminate, this, _1, _2, + boost::ref(signalSet))); + } + } + +private: + shared_ptr m_ribManager; + + static const std::string SUPPORTED_CONFIG_SECTIONS[]; + static const size_t N_SUPPORTED_CONFIG_SECTIONS; +}; + +const std::string Nrd::SUPPORTED_CONFIG_SECTIONS[] = + { + "log", + "security", + }; + +const size_t Nrd::N_SUPPORTED_CONFIG_SECTIONS = + sizeof(SUPPORTED_CONFIG_SECTIONS) / sizeof(std::string); + +} // namespace rib +} // namespace nfd + +int +main(int argc, char** argv) +{ + using namespace nfd::rib; + + ProgramOptions options; + bool isCommandLineValid = Nrd::parseCommandLine(argc, argv, options); + if (!isCommandLineValid) { + Nrd::printUsage(std::cerr, argv[0]); + return 1; + } + if (options.showUsage) { + Nrd::printUsage(std::cout, argv[0]); + return 0; + } + + if (options.showModules) { + Nrd::printModules(std::cout); + return 0; + } + + Nrd nrdInstance; + + try { + nrdInstance.initialize(options.config); + } + catch (boost::filesystem::filesystem_error& e) { + if (e.code() == boost::system::errc::permission_denied) { + NFD_LOG_FATAL("Permissions denied for " << e.path1() << ". " << + argv[0] << " should be run as superuser"); + } + else { + NFD_LOG_FATAL(e.what()); + } + return 1; + } + catch (const std::exception& e) { + NFD_LOG_FATAL(e.what()); + return 2; + } + + boost::asio::signal_set signalSet(nrdInstance.getIoService()); + signalSet.add(SIGINT); + signalSet.add(SIGTERM); + signalSet.add(SIGHUP); + signalSet.add(SIGUSR1); + signalSet.add(SIGUSR2); + signalSet.async_wait(bind(&Nrd::terminate, &nrdInstance, _1, _2, + boost::ref(signalSet))); + + try { + nrdInstance.getIoService().run(); + } + catch (std::exception& e) { + NFD_LOG_FATAL(e.what()); + return 3; + } + + return 0; +} diff --git a/rib/rib-manager.cpp b/rib/rib-manager.cpp new file mode 100644 index 00000000..1a3e41f1 --- /dev/null +++ b/rib/rib-manager.cpp @@ -0,0 +1,341 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/** + * Copyright (c) 2014 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 . + **/ + +#include "rib-manager.hpp" + +namespace nfd { +namespace rib { + +const Name RibManager::COMMAND_PREFIX = "/localhost/nrd"; +const Name RibManager::REMOTE_COMMAND_PREFIX = "/localhop/nrd"; + +const size_t RibManager::COMMAND_UNSIGNED_NCOMPS = + RibManager::COMMAND_PREFIX.size() + + 1 + // verb + 1; // verb options + +const size_t RibManager::COMMAND_SIGNED_NCOMPS = + RibManager::COMMAND_UNSIGNED_NCOMPS + + 4; // (timestamp, nonce, signed info tlv, signature tlv) + +const RibManager::VerbAndProcessor RibManager::COMMAND_VERBS[] = + { + VerbAndProcessor( + Name::Component("register"), + &RibManager::insertEntry + ), + + VerbAndProcessor( + Name::Component("unregister"), + &RibManager::deleteEntry + ), + }; + +RibManager::RibManager() + : m_face(new ndn::Face()) + , m_nfdController(new ndn::nfd::Controller(*m_face)) + , m_validator(m_face) + , m_faceMonitor(*m_face) + , m_verbDispatch(COMMAND_VERBS, + COMMAND_VERBS + (sizeof(COMMAND_VERBS) / sizeof(VerbAndProcessor))) +{ +} + +void +RibManager::registerWithNfd() +{ + //check whether the components of localhop and localhost prefixes are same + BOOST_ASSERT(COMMAND_PREFIX.size() == REMOTE_COMMAND_PREFIX.size()); + + std::cerr << "Setting interest filter on: " << COMMAND_PREFIX.toUri() << std::endl; + m_face->setController(m_nfdController); + m_face->setInterestFilter(COMMAND_PREFIX.toUri(), + bind(&RibManager::onRibRequest, this, _2), + bind(&RibManager::setInterestFilterFailed, this, _1, _2)); + + std::cerr << "Setting interest filter on: " << REMOTE_COMMAND_PREFIX.toUri() << std::endl; + m_face->setInterestFilter(REMOTE_COMMAND_PREFIX.toUri(), + bind(&RibManager::onRibRequest, this, _2), + bind(&RibManager::setInterestFilterFailed, this, _1, _2)); + + std::cerr << "Monitoring faces" << std::endl; + m_faceMonitor.addSubscriber(boost::bind(&RibManager::onNotification, this, _1)); + m_faceMonitor.startNotifications(); +} + +void +RibManager::setConfigFile(ConfigFile& configFile) +{ + configFile.addSectionHandler("security", + bind(&RibManager::onConfig, this, _1, _2, _3)); +} + +void +RibManager::onConfig(const ConfigSection& configSection, + bool isDryRun, + const std::string& filename) +{ + /// \todo remove check after validator-conf replaces settings on each load + if (!isDryRun) + m_validator.load(configSection, filename); +} + +void +RibManager::setInterestFilterFailed(const Name& name, const std::string& msg) +{ + std::cerr << "Error in setting interest filter (" << name << "): " << msg << std::endl; + m_face->shutdown(); +} + +void +RibManager::sendResponse(const Name& name, + const ControlResponse& response) +{ + const Block& encodedControl = response.wireEncode(); + + Data responseData(name); + responseData.setContent(encodedControl); + + m_keyChain.sign(responseData); + m_face->put(responseData); +} + +void +RibManager::sendResponse(const Name& name, + uint32_t code, + const std::string& text) +{ + ControlResponse response(code, text); + sendResponse(name, response); +} + +void +RibManager::onRibRequest(const Interest& request) +{ + m_validator.validate(request, + bind(&RibManager::onRibRequestValidated, this, _1), + bind(&RibManager::onRibRequestValidationFailed, this, _1, _2)); +} + +void +RibManager::onRibRequestValidated(const shared_ptr& request) +{ + const Name& command = request->getName(); + + //REMOTE_COMMAND_PREFIX number of componenets are same as + // NRD_COMMAND_PREFIX's so no extra checks are required. + const Name::Component& verb = command.get(COMMAND_PREFIX.size()); + VerbDispatchTable::const_iterator verbProcessor = m_verbDispatch.find(verb); + + if (verbProcessor != m_verbDispatch.end()) + { + PrefixRegOptions options; + if (!extractOptions(*request, options)) + { + sendResponse(command, 400, "Malformed command"); + return; + } + + /// \todo authorize command + if (false) + { + sendResponse(request->getName(), 403, "Unauthorized command"); + return; + } + + // \todo add proper log support + std::cout << "Received options (name, faceid, cost): " << options.getName() << + ", " << options.getFaceId() << ", " << options.getCost() << std::endl; + + ControlResponse response; + (verbProcessor->second)(this, *request, options); + } + else + { + sendResponse(request->getName(), 501, "Unsupported command"); + } +} + +void +RibManager::onRibRequestValidationFailed(const shared_ptr& request, + const std::string& failureInfo) +{ + sendResponse(request->getName(), 403, failureInfo); +} + +bool +RibManager::extractOptions(const Interest& request, + PrefixRegOptions& extractedOptions) +{ + // const Name& command = request.getName(); + //REMOTE_COMMAND_PREFIX is same in size of NRD_COMMAND_PREFIX + //so no extra processing is required. + const size_t optionCompIndex = COMMAND_PREFIX.size() + 1; + + try + { + Block rawOptions = request.getName()[optionCompIndex].blockFromValue(); + extractedOptions.wireDecode(rawOptions); + } + catch (const ndn::Tlv::Error& e) + { + return false; + } + + if (extractedOptions.getFaceId() == 0) + { + std::cout <<"IncomingFaceId: " << request.getIncomingFaceId() << std::endl; + extractedOptions.setFaceId(request.getIncomingFaceId()); + } + return true; +} + +void +RibManager::onCommandError(uint32_t code, const std::string& error, + const Interest& request, + const PrefixRegOptions& options) +{ + std::cout << "NFD Error: " << error << " (code: " << code << ")" << std::endl; + + ControlResponse response; + + if (code == 404) + { + response.setCode(code); + response.setText(error); + } + else + { + response.setCode(533); + std::ostringstream os; + os << "Failure to update NFD " << "(NFD Error: " << code << " " << error << ")"; + response.setText(os.str()); + } + + sendResponse(request.getName(), response); + m_managedRib.erase(options); +} + +void +RibManager::onUnRegSuccess(const Interest& request, const PrefixRegOptions& options) +{ + ControlResponse response; + + response.setCode(200); + response.setText("Success"); + response.setBody(options.wireEncode()); + + std::cout << "Success: Name unregistered (" << + options.getName() << ", " << + options.getFaceId() << ")" << std::endl; + sendResponse(request.getName(), response); + m_managedRib.erase(options); +} + +void +RibManager::onRegSuccess(const Interest& request, const PrefixRegOptions& options) +{ + ControlResponse response; + + response.setCode(200); + response.setText("Success"); + response.setBody(options.wireEncode()); + + std::cout << "Success: Name registered (" << options.getName() << ", " << + options.getFaceId() << ")" << std::endl; + sendResponse(request.getName(), response); +} + +void +RibManager::insertEntry(const Interest& request, const PrefixRegOptions& options) +{ + // For right now, just pass the options to fib as it is, + // without processing flags. Later options will be first added to + // Rib tree, then nrd will generate fib updates based on flags and then + // will add next hops one by one.. + m_managedRib.insert(options); + m_nfdController->start( + ControlParameters() + .setName(options.getName()) + .setFaceId(options.getFaceId()) + .setCost(options.getCost()), + bind(&RibManager::onRegSuccess, this, request, options), + bind(&RibManager::onCommandError, this, _1, _2, request, options)); +} + +void +RibManager::deleteEntry(const Interest& request, const PrefixRegOptions& options) +{ + m_nfdController->start( + ControlParameters() + .setName(options.getName()) + .setFaceId(options.getFaceId()), + bind(&RibManager::onUnRegSuccess, this, request, options), + bind(&RibManager::onCommandError, this, _1, _2, request, options)); +} + +boost::asio::io_service& +RibManager::getIoService() +{ + /// \todo Switch face to use global io service (needs library update) + return *m_face->ioService(); +} + +void +RibManager::onControlHeaderSuccess() +{ + std::cout << "Local control header enabled" << std::endl; +} + +void +RibManager::onControlHeaderError(uint32_t code, const std::string& reason) +{ + std::cout << "Error: couldn't enable local control header " + << "(code: " << code << ", info: " << reason << ")" << std::endl; + m_face->shutdown(); +} + +void +RibManager::enableLocalControlHeader() +{ + m_nfdController->start( + ControlParameters() + .setLocalControlFeature(ndn::nfd::LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID), + bind(&RibManager::onControlHeaderSuccess, this), + bind(&RibManager::onControlHeaderError, this, _1, _2)); +} + +void +RibManager::onNotification(const FaceEventNotification& notification) +{ + /// \todo A notification can be missed, in this case check Facelist + std::cerr << "Notification Rcvd: " << notification << std::endl; + if (notification.getKind() == ndn::nfd::FACE_EVENT_DESTROYED) { //face destroyed + m_managedRib.erase(notification.getFaceId()); + } +} + +} // namespace rib +} // namespace nfd diff --git a/rib/rib-manager.hpp b/rib/rib-manager.hpp new file mode 100644 index 00000000..94792919 --- /dev/null +++ b/rib/rib-manager.hpp @@ -0,0 +1,157 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/** + * Copyright (c) 2014 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 . + **/ + +#ifndef NFD_RIB_RIB_MANAGER_HPP +#define NFD_RIB_RIB_MANAGER_HPP + +#include "rib.hpp" +#include "face-monitor.hpp" +#include "core/config-file.hpp" + +#include +#include +#include +#include +#include + +namespace nfd { +namespace rib { + +using ndn::nfd::ControlCommand; +using ndn::nfd::ControlResponse; +using ndn::nfd::ControlParameters; + +class RibManager : noncopyable +{ +public: + RibManager(); + + void + onRibRequest(const Interest& request); + + void + registerWithNfd(); + + void + enableLocalControlHeader(); + + boost::asio::io_service& + getIoService(); + + void + setConfigFile(ConfigFile& configFile); + +private: + void + onConfig(const ConfigSection& configSection, + bool isDryRun, + const std::string& filename); + + void + sendResponse(const Name& name, + const ControlResponse& response); + + void + sendResponse(const Name& name, + uint32_t code, + const std::string& text); + + void + onRibRequestValidated(const shared_ptr& request); + + void + onRibRequestValidationFailed(const shared_ptr& request, + const std::string& failureInfo); + + void + onCommandError(uint32_t code, const std::string& error, + const ndn::Interest& interest, + const PrefixRegOptions& options); + + void + onRegSuccess(const ndn::Interest& interest, const PrefixRegOptions& options); + + void + onUnRegSuccess(const ndn::Interest& interest, const PrefixRegOptions& options); + + void + onControlHeaderSuccess(); + + void + onControlHeaderError(uint32_t code, const std::string& reason); + + void + setInterestFilterFailed(const Name& name, const std::string& msg); + + void + insertEntry(const Interest& request, const PrefixRegOptions& options); + + void + deleteEntry(const Interest& request, const PrefixRegOptions& options); + + bool + extractOptions(const Interest& request, + PrefixRegOptions& extractedOptions); + + void + onNotification(const FaceEventNotification& notification); + +private: + Rib m_managedRib; + ndn::shared_ptr m_face; + ndn::shared_ptr m_nfdController; + ndn::KeyChain m_keyChain; + ndn::ValidatorConfig m_validator; + FaceMonitor m_faceMonitor; + + typedef boost::function VerbProcessor; + + typedef std::map VerbDispatchTable; + + typedef std::pair VerbAndProcessor; + + + const VerbDispatchTable m_verbDispatch; + + static const Name COMMAND_PREFIX; // /localhost/nrd + static const Name REMOTE_COMMAND_PREFIX; // /localhop/nrd + + // number of components in an invalid, but not malformed, unsigned command. + // (/localhost/nrd + verb + options) = 4 + static const size_t COMMAND_UNSIGNED_NCOMPS; + + // number of components in a valid signed Interest. + // 8 with signed Interest support. + static const size_t COMMAND_SIGNED_NCOMPS; + + static const VerbAndProcessor COMMAND_VERBS[]; +}; + +} // namespace rib +} // namespace nfd + +#endif // NFD_RIB_RIB_MANAGER_HPP diff --git a/rib/src/rib.cpp b/rib/rib.cpp similarity index 58% rename from rib/src/rib.cpp rename to rib/rib.cpp index 4f1387b9..8ad5ec6c 100644 --- a/rib/src/rib.cpp +++ b/rib/rib.cpp @@ -1,13 +1,32 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /** - * Copyright (C) 2014 Named Data Networking Project - * See COPYING for copyright and distribution information. - */ + * Copyright (c) 2014 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 . + **/ #include "rib.hpp" -namespace ndn { -namespace nrd { +namespace nfd { +namespace rib { Rib::Rib() { @@ -86,5 +105,5 @@ Rib::erase(uint64_t faceId) } } -} // namespace nrd -} // namespace ndn +} // namespace rib +} // namespace nfd diff --git a/rib/rib.hpp b/rib/rib.hpp new file mode 100644 index 00000000..745a7609 --- /dev/null +++ b/rib/rib.hpp @@ -0,0 +1,108 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/** + * Copyright (c) 2014 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 . + **/ + +#ifndef NFD_RIB_RIB_HPP +#define NFD_RIB_RIB_HPP + +#include "common.hpp" + +#include + +namespace nfd { +namespace rib { + +using ndn::nrd::PrefixRegOptions; +namespace tlv = ndn::tlv; + +/** \brief represents the RIB + */ +class Rib +{ +public: + typedef std::list RibTable; + typedef RibTable::const_iterator const_iterator; + + Rib(); + + ~Rib(); + + const_iterator + find(const PrefixRegOptions& options) const; + + void + insert(const PrefixRegOptions& options); + + void + erase(const PrefixRegOptions& options); + + void + erase(uint64_t faceId); + + const_iterator + begin() const; + + const_iterator + end() const; + + size_t + size() const; + + size_t + empty() const; + +private: + // Note: Taking a list right now. A Trie will be used later to store + // prefixes + RibTable m_rib; +}; + +inline Rib::const_iterator +Rib::begin() const +{ + return m_rib.begin(); +} + +inline Rib::const_iterator +Rib::end() const +{ + return m_rib.end(); +} + +inline size_t +Rib::size() const +{ + return m_rib.size(); +} + +inline size_t +Rib::empty() const +{ + return m_rib.empty(); +} + +} // namespace rib +} // namespace nfd + +#endif // NFD_RIB_RIB_HPP diff --git a/rib/src/common.hpp b/rib/src/common.hpp deleted file mode 100644 index f5208285..00000000 --- a/rib/src/common.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/** - * Copyright (C) 2014 Named Data Networking Project - * See COPYING for copyright and distribution information. - */ - -#ifndef NRD_COMMON_HPP -#define NRD_COMMON_HPP - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "../build/src/config.hpp" - -namespace ndn { -namespace nrd { - -class Error : public std::runtime_error -{ -public: - explicit - Error(const std::string& what) - : std::runtime_error(what) - { - } -}; - -} //namespace nrd -} //namespace ndn -#endif // NRD_COMMON_HPP diff --git a/rib/src/face-monitor.hpp b/rib/src/face-monitor.hpp deleted file mode 100644 index e0eeaa60..00000000 --- a/rib/src/face-monitor.hpp +++ /dev/null @@ -1,72 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */ -/** - * Copyright (C) 2014 Regents of the University of California. - * See COPYING for copyright and distribution information. - */ -#ifndef FACE_MONITOR_HPP -#define FACE_MONITOR_HPP - -#include "common.hpp" -#include -#include - -namespace ndn { - -class FaceMonitor -{ -public: - typedef function NotificationCallback; - typedef function TimeoutCallback; - - typedef std::vector NotificationCallbacks; - typedef std::vector TimeoutCallbacks; - - explicit - FaceMonitor(Face& face); - - ~FaceMonitor(); - - /** \brief Stops all notifications. This method doesn't remove registered callbacks. - */ - void - stopNotifications(); - - /** \brief Resumes notifications for added subscribers. - */ - void - startNotifications(); - - /** \brief Removes all notification subscribers. - */ - void - removeAllSubscribers(); - - /** \brief Adds a notification subscriber. This method doesn't return on timeouts. - */ - void - addSubscriber(const NotificationCallback& notificationCallback); - - /** \brief Adds a notification subscriber. - */ - void - addSubscriber(const NotificationCallback& notificationCallback, - const TimeoutCallback& timeoutCallback); - -private: - void - onTimeout(); - - void - onNotification(const Data& data); - -private: - Face& m_face; - uint64_t m_lastSequence; - bool m_isStopped; - NotificationCallbacks m_notificationCallbacks; - TimeoutCallbacks m_timeoutCallbacks; - const PendingInterestId* m_lastInterestId; -}; - -}//namespace ndn -#endif //FACE_MONITOR_HPP diff --git a/rib/src/main.cpp b/rib/src/main.cpp deleted file mode 100644 index 5d0e5ed3..00000000 --- a/rib/src/main.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/** - * Copyright (C) 2014 Named Data Networking Project - * See COPYING for copyright and distribution information. - */ - -#include "common.hpp" -#include "nrd.hpp" -#include "nrd-config.hpp" -#include - -struct ProgramOptions -{ - bool showUsage; - std::string config; -}; - -static void -printUsage(std::ostream& os, const std::string& programName) -{ - os << "Usage: \n" - << " " << programName << " [options]\n" - << "\n" - << "Run NRD daemon\n" - << "\n" - << "Options:\n" - << " [--help] - print this help message\n" - << " [--config /path/to/nrd.conf] - path to configuration file " - << "(default: " << DEFAULT_CONFIG_FILE << ")\n" - ; -} - -static bool -parseCommandLine(int argc, char** argv, ProgramOptions& options) -{ - options.showUsage = false; - options.config = DEFAULT_CONFIG_FILE; - - while (true) { - int optionIndex = 0; - static ::option longOptions[] = { - { "help" , no_argument , 0, 0 }, - { "config" , required_argument, 0, 0 }, - { 0 , 0 , 0, 0 } - }; - int c = getopt_long_only(argc, argv, "", longOptions, &optionIndex); - if (c == -1) - break; - - switch (c) { - case 0: - switch (optionIndex) { - case 0: // help - options.showUsage = true; - break; - case 1: // config - options.config = ::optarg; - break; - default: - return false; - } - break; - } - } - return true; -} - -int -main(int argc, char** argv) -{ - //processing command line arguments, if available - ProgramOptions options; - bool isCommandLineValid = parseCommandLine(argc, argv, options); - if (!isCommandLineValid) { - printUsage(std::cerr, argv[0]); - return 1; - } - if (options.showUsage) { - printUsage(std::cout, argv[0]); - return 0; - } - - //Now read the config file and pass the security section to validator - try { - std::string configFilename(options.config); - - ndn::nrd::NrdConfig config; - config.load(configFilename); - ndn::nrd::ConfigSection securitySection = config.getSecuritySection(); - - ndn::nrd::Nrd nrd(securitySection, configFilename); - nrd.enableLocalControlHeader(); - nrd.listen(); - } - catch (std::exception& e) { - std::cerr << "ERROR: " << e.what() << std::endl; - } - - return 0; -} diff --git a/rib/src/nrd-config.cpp b/rib/src/nrd-config.cpp deleted file mode 100644 index c6181f9c..00000000 --- a/rib/src/nrd-config.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include "nrd-config.hpp" - -namespace ndn { -namespace nrd { - -NrdConfig::NrdConfig() - : m_isSecuritySectionDefined(false) -{ -} - -void -NrdConfig::load(const std::string& filename) -{ - std::ifstream inputFile; - inputFile.open(filename.c_str()); - if (!inputFile.good() || !inputFile.is_open()) - { - std::string msg = "Failed to read configuration file: "; - msg += filename; - std::cerr << filename << std::endl; - throw Error(msg); - } - load(inputFile, filename); - inputFile.close(); -} - -void -NrdConfig::load(const std::string& input, const std::string& filename) -{ - std::istringstream inputStream(input); - load(inputStream, filename); -} - -void -NrdConfig::load(std::istream& input, const std::string& filename) -{ - BOOST_ASSERT(!filename.empty()); - - ConfigSection ptree; - try - { - boost::property_tree::read_info(input, ptree); - } - catch (const boost::property_tree::info_parser_error& error) - { - std::stringstream msg; - msg << "Failed to parse configuration file"; - msg << " " << filename; - msg << " " << error.message() << " line " << error.line(); - throw Error(msg.str()); - } - process(ptree, filename); -} - -void -NrdConfig::process(const ConfigSection& configSection, - const std::string& filename) -{ - BOOST_ASSERT(!filename.empty()); - - if (configSection.begin() == configSection.end()) - { - std::string msg = "Error processing configuration file"; - msg += ": "; - msg += filename; - msg += " no data"; - throw Error(msg); - } - - for (ConfigSection::const_iterator i = configSection.begin(); - i != configSection.end(); ++i) - { - const std::string& sectionName = i->first; - const ConfigSection& section = i->second; - - if (boost::iequals(sectionName, "security")) - { - onSectionSecurity(section, filename); - } - //Add more sections here as needed - //else if (boost::iequals(sectionName, "another-section")) - //{ - //onSectionAnotherSection(section, filename); - //} - else - { - std::string msg = "Error processing configuration file"; - msg += " "; - msg += filename; - msg += " unrecognized section: " + sectionName; - throw Error(msg); - } - } -} - -void -NrdConfig::onSectionSecurity(const ConfigSection& section, - const std::string& filename) -{ - if (!m_isSecuritySectionDefined) { - //setSecturitySection(section); - m_securitySection = section; - m_filename = filename; - m_isSecuritySectionDefined = true; - } - else { - std::string msg = "Error processing configuration file"; - msg += " "; - msg += filename; - msg += " security section can appear only once"; - throw Error(msg); - } -} - -} //namespace nrd -} //namespace ndn diff --git a/rib/src/nrd-config.hpp b/rib/src/nrd-config.hpp deleted file mode 100644 index ee119f68..00000000 --- a/rib/src/nrd-config.hpp +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef NRD_CONFIG_HPP -#define NRD_CONFIG_HPP - -#include "common.hpp" - -namespace ndn { -namespace nrd { - -typedef boost::property_tree::ptree ConfigSection; - -class NrdConfig -{ - -public: - NrdConfig(); - - virtual - ~NrdConfig() - { - } - - void - load(const std::string& filename); - - void - load(const std::string& input, const std::string& filename); - - void - load(std::istream& input, const std::string& filename); - - const ConfigSection& - getSecuritySection() const - { - return m_securitySection; - } - -private: - void - process(const ConfigSection& configSection, - const std::string& filename); - - void - onSectionSecurity(const ConfigSection& section, - const std::string& filename); - - const void - setSecturitySection(const ConfigSection& securitySection) - { - m_securitySection = securitySection; - } - -private: - bool m_isSecuritySectionDefined; - ConfigSection m_securitySection; - std::string m_filename; -}; - -}//namespace nrd -}//namespace ndn - -#endif // NRD_CONFIG_HPP diff --git a/rib/src/nrd.cpp b/rib/src/nrd.cpp deleted file mode 100644 index 492f9c2c..00000000 --- a/rib/src/nrd.cpp +++ /dev/null @@ -1,304 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/** - * Copyright (C) 2014 Named Data Networking Project - * See COPYING for copyright and distribution information. - */ - -#include "nrd.hpp" - -namespace ndn { -namespace nrd { - -const Name Nrd::COMMAND_PREFIX = "/localhost/nrd"; -const Name Nrd::REMOTE_COMMAND_PREFIX = "/localhop/nrd"; - -const size_t Nrd::COMMAND_UNSIGNED_NCOMPS = - Nrd::COMMAND_PREFIX.size() + - 1 + // verb - 1; // verb options - -const size_t Nrd::COMMAND_SIGNED_NCOMPS = - Nrd::COMMAND_UNSIGNED_NCOMPS + - 4; // (timestamp, nonce, signed info tlv, signature tlv) - -const Nrd::VerbAndProcessor Nrd::COMMAND_VERBS[] = - { - VerbAndProcessor( - Name::Component("register"), - &Nrd::insertEntry - ), - - VerbAndProcessor( - Name::Component("unregister"), - &Nrd::deleteEntry - ), - }; - -Nrd::Nrd(const ndn::nrd::ConfigSection& securitySection, - const std::string& validatorConfig) - : m_face(new Face()) - , m_nfdController(new nfd::Controller(*m_face)) - , m_validator(m_face) - , m_faceMonitor(*m_face) - , m_verbDispatch(COMMAND_VERBS, - COMMAND_VERBS + (sizeof(COMMAND_VERBS) / sizeof(VerbAndProcessor))) -{ - //check whether the components of localhop and localhost prefixes are same - BOOST_ASSERT(COMMAND_PREFIX.size() == REMOTE_COMMAND_PREFIX.size()); - - //m_validator.load(validatorConfig); - m_validator.load(securitySection, validatorConfig); - - std::cerr << "Setting interest filter on: " << COMMAND_PREFIX.toUri() << std::endl; - m_face->setController(m_nfdController); - m_face->setInterestFilter(COMMAND_PREFIX.toUri(), - bind(&Nrd::onRibRequest, this, _2), - bind(&Nrd::setInterestFilterFailed, this, _1, _2)); - - std::cerr << "Setting interest filter on: " << REMOTE_COMMAND_PREFIX.toUri() << std::endl; - m_face->setInterestFilter(REMOTE_COMMAND_PREFIX.toUri(), - bind(&Nrd::onRibRequest, this, _2), - bind(&Nrd::setInterestFilterFailed, this, _1, _2)); - - std::cerr << "Monitoring faces" << std::endl; - m_faceMonitor.addSubscriber(boost::bind(&Nrd::onNotification, this, _1)); - m_faceMonitor.startNotifications(); -} - -void -Nrd::setInterestFilterFailed(const Name& name, const std::string& msg) -{ - std::cerr << "Error in setting interest filter (" << name << "): " << msg << std::endl; - m_face->shutdown(); -} - -void -Nrd::sendResponse(const Name& name, - const nfd::ControlResponse& response) -{ - const Block& encodedControl = response.wireEncode(); - - Data responseData(name); - responseData.setContent(encodedControl); - - m_keyChain.sign(responseData); - m_face->put(responseData); -} - -void -Nrd::sendResponse(const Name& name, - uint32_t code, - const std::string& text) -{ - nfd::ControlResponse response(code, text); - sendResponse(name, response); -} - -void -Nrd::onRibRequest(const Interest& request) -{ - m_validator.validate(request, - bind(&Nrd::onRibRequestValidated, this, _1), - bind(&Nrd::onRibRequestValidationFailed, this, _1, _2)); -} - -void -Nrd::onRibRequestValidated(const shared_ptr& request) -{ - const Name& command = request->getName(); - - //REMOTE_COMMAND_PREFIX number of componenets are same as - // NRD_COMMAND_PREFIX's so no extra checks are required. - const Name::Component& verb = command.get(COMMAND_PREFIX.size()); - VerbDispatchTable::const_iterator verbProcessor = m_verbDispatch.find(verb); - - if (verbProcessor != m_verbDispatch.end()) - { - PrefixRegOptions options; - if (!extractOptions(*request, options)) - { - sendResponse(command, 400, "Malformed command"); - return; - } - - /// \todo authorize command - if (false) - { - sendResponse(request->getName(), 403, "Unauthorized command"); - return; - } - - // \todo add proper log support - std::cout << "Received options (name, faceid, cost): " << options.getName() << - ", " << options.getFaceId() << ", " << options.getCost() << std::endl; - - nfd::ControlResponse response; - (verbProcessor->second)(this, *request, options); - } - else - { - sendResponse(request->getName(), 501, "Unsupported command"); - } -} - -void -Nrd::onRibRequestValidationFailed(const shared_ptr& request, - const std::string& failureInfo) -{ - sendResponse(request->getName(), 403, failureInfo); -} - -bool -Nrd::extractOptions(const Interest& request, - PrefixRegOptions& extractedOptions) -{ - // const Name& command = request.getName(); - //REMOTE_COMMAND_PREFIX is same in size of NRD_COMMAND_PREFIX - //so no extra processing is required. - const size_t optionCompIndex = COMMAND_PREFIX.size() + 1; - - try - { - Block rawOptions = request.getName()[optionCompIndex].blockFromValue(); - extractedOptions.wireDecode(rawOptions); - } - catch (const ndn::Tlv::Error& e) - { - return false; - } - - if (extractedOptions.getFaceId() == 0) - { - std::cout <<"IncomingFaceId: " << request.getIncomingFaceId() << std::endl; - extractedOptions.setFaceId(request.getIncomingFaceId()); - } - return true; -} - -void -Nrd::onCommandError(uint32_t code, const std::string& error, - const Interest& request, - const PrefixRegOptions& options) -{ - std::cout << "NFD Error: " << error << " (code: " << code << ")" << std::endl; - - nfd::ControlResponse response; - - if (code == 404) - { - response.setCode(code); - response.setText(error); - } - else - { - response.setCode(533); - std::ostringstream os; - os << "Failure to update NFD " << "(NFD Error: " << code << " " << error << ")"; - response.setText(os.str()); - } - - sendResponse(request.getName(), response); - m_managedRib.erase(options); -} - -void -Nrd::onUnRegSuccess(const Interest& request, const PrefixRegOptions& options) -{ - nfd::ControlResponse response; - - response.setCode(200); - response.setText("Success"); - response.setBody(options.wireEncode()); - - std::cout << "Success: Name unregistered (" << - options.getName() << ", " << - options.getFaceId() << ")" << std::endl; - sendResponse(request.getName(), response); - m_managedRib.erase(options); -} - -void -Nrd::onRegSuccess(const Interest& request, const PrefixRegOptions& options) -{ - nfd::ControlResponse response; - - response.setCode(200); - response.setText("Success"); - response.setBody(options.wireEncode()); - - std::cout << "Success: Name registered (" << options.getName() << ", " << - options.getFaceId() << ")" << std::endl; - sendResponse(request.getName(), response); -} - -void -Nrd::insertEntry(const Interest& request, const PrefixRegOptions& options) -{ - // For right now, just pass the options to fib as it is, - // without processing flags. Later options will be first added to - // Rib tree, then nrd will generate fib updates based on flags and then - // will add next hops one by one.. - m_managedRib.insert(options); - m_nfdController->start( - nfd::ControlParameters() - .setName(options.getName()) - .setFaceId(options.getFaceId()) - .setCost(options.getCost()), - bind(&Nrd::onRegSuccess, this, request, options), - bind(&Nrd::onCommandError, this, _1, _2, request, options)); -} - -void -Nrd::deleteEntry(const Interest& request, const PrefixRegOptions& options) -{ - m_nfdController->start( - nfd::ControlParameters() - .setName(options.getName()) - .setFaceId(options.getFaceId()), - bind(&Nrd::onUnRegSuccess, this, request, options), - bind(&Nrd::onCommandError, this, _1, _2, request, options)); -} - -void -Nrd::listen() -{ - std::cout << "NRD started: listening for incoming interests" << std::endl; - m_face->processEvents(); -} - -void -Nrd::onControlHeaderSuccess() -{ - std::cout << "Local control header enabled" << std::endl; -} - -void -Nrd::onControlHeaderError(uint32_t code, const std::string& reason) -{ - std::cout << "Error: couldn't enable local control header " - << "(code: " << code << ", info: " << reason << ")" << std::endl; - m_face->shutdown(); -} - -void -Nrd::enableLocalControlHeader() -{ - m_nfdController->start( - nfd::ControlParameters() - .setLocalControlFeature(nfd::LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID), - bind(&Nrd::onControlHeaderSuccess, this), - bind(&Nrd::onControlHeaderError, this, _1, _2)); -} - -void -Nrd::onNotification(const nfd::FaceEventNotification& notification) -{ - /// \todo A notification can be missed, in this case check Facelist - std::cerr << "Notification Rcvd: " << notification << std::endl; - if (notification.getKind() == ndn::nfd::FACE_EVENT_DESTROYED) { //face destroyed - m_managedRib.erase(notification.getFaceId()); - } -} - -} // namespace nrd -} // namespace ndn diff --git a/rib/src/nrd.hpp b/rib/src/nrd.hpp deleted file mode 100644 index d6007d78..00000000 --- a/rib/src/nrd.hpp +++ /dev/null @@ -1,118 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/** - * Copyright (C) 2014 Named Data Networking Project - * See COPYING for copyright and distribution information. - */ - -#ifndef NRD_HPP -#define NRD_HPP - -#include "rib.hpp" -#include "face-monitor.hpp" -#include "nrd-config.hpp" - -namespace ndn { -namespace nrd { - -class Nrd : noncopyable -{ -public: - Nrd(const ndn::nrd::ConfigSection& securitySection, - const std::string& validatorConfig); - - void - onRibRequest(const Interest& request); - - void - enableLocalControlHeader(); - - void - listen(); - -private: - void - sendResponse(const Name& name, - const nfd::ControlResponse& response); - - void - sendResponse(const Name& name, - uint32_t code, - const std::string& text); - - void - onRibRequestValidated(const shared_ptr& request); - - void - onRibRequestValidationFailed(const shared_ptr& request, - const std::string& failureInfo); - - void - onCommandError(uint32_t code, const std::string& error, - const ndn::Interest& interest, - const PrefixRegOptions& options); - - void - onRegSuccess(const ndn::Interest& interest, const PrefixRegOptions& options); - - void - onUnRegSuccess(const ndn::Interest& interest, const PrefixRegOptions& options); - - void - onControlHeaderSuccess(); - - void - onControlHeaderError(uint32_t code, const std::string& reason); - - void - setInterestFilterFailed(const Name& name, const std::string& msg); - - void - insertEntry(const Interest& request, const PrefixRegOptions& options); - - void - deleteEntry(const Interest& request, const PrefixRegOptions& options); - - bool - extractOptions(const Interest& request, - PrefixRegOptions& extractedOptions); - - void - onNotification(const nfd::FaceEventNotification& notification); - -private: - Rib m_managedRib; - ndn::shared_ptr m_face; - ndn::shared_ptr m_nfdController; - ndn::KeyChain m_keyChain; - ndn::ValidatorConfig m_validator; - ndn::FaceMonitor m_faceMonitor; - - typedef boost::function VerbProcessor; - - typedef std::map VerbDispatchTable; - - typedef std::pair VerbAndProcessor; - - - const VerbDispatchTable m_verbDispatch; - - static const Name COMMAND_PREFIX; // /localhost/nrd - static const Name REMOTE_COMMAND_PREFIX; // /localhop/nrd - - // number of components in an invalid, but not malformed, unsigned command. - // (/localhost/nrd + verb + options) = 4 - static const size_t COMMAND_UNSIGNED_NCOMPS; - - // number of components in a valid signed Interest. - // 5 in mock (see UNSIGNED_NCOMPS), 8 with signed Interest support. - static const size_t COMMAND_SIGNED_NCOMPS; - - static const VerbAndProcessor COMMAND_VERBS[]; -}; - -} // namespace nrd -} // namespace ndn - -#endif // NRD_HPP diff --git a/rib/src/rib.hpp b/rib/src/rib.hpp deleted file mode 100644 index 3196817f..00000000 --- a/rib/src/rib.hpp +++ /dev/null @@ -1,84 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/** - * Copyright (C) 2014 Named Data Networking Project - * See COPYING for copyright and distribution information. - */ - -#ifndef NRD_RIB_HPP -#define NRD_RIB_HPP - -#include "common.hpp" - -namespace ndn { -namespace nrd { - -/** \brief represents the RIB - */ -class Rib -{ -public: - typedef std::list RibTable; - typedef RibTable::const_iterator const_iterator; - - Rib(); - - ~Rib(); - - const_iterator - find(const PrefixRegOptions& options) const; - - void - insert(const PrefixRegOptions& options); - - void - erase(const PrefixRegOptions& options); - - void - erase(uint64_t faceId); - - const_iterator - begin() const; - - const_iterator - end() const; - - size_t - size() const; - - size_t - empty() const; - -private: - // Note: Taking a list right now. A Trie will be used later to store - // prefixes - RibTable m_rib; -}; - -inline Rib::const_iterator -Rib::begin() const -{ - return m_rib.begin(); -} - -inline Rib::const_iterator -Rib::end() const -{ - return m_rib.end(); -} - -inline size_t -Rib::size() const -{ - return m_rib.size(); -} - -inline size_t -Rib::empty() const -{ - return m_rib.empty(); -} - -} // namespace nrd -} // namespace ndn - -#endif // NRD_RIB_HPP diff --git a/rib/tests/main.cpp b/rib/tests/main.cpp deleted file mode 100644 index a7e827d3..00000000 --- a/rib/tests/main.cpp +++ /dev/null @@ -1,10 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/** - * Copyright (C) 2014 Named Data Networking Project - * See COPYING for copyright and distribution information. - */ - -#define BOOST_TEST_MAIN 1 -#define BOOST_TEST_DYN_LINK 1 - -#include diff --git a/rib/tests/test-rib.cpp b/rib/tests/test-rib.cpp deleted file mode 100644 index 920c606e..00000000 --- a/rib/tests/test-rib.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/** - * Copyright (C) 2014 Named Data Networking Project - * See COPYING for copyright and distribution information. - */ - -#include "rib.hpp" - -#include - -namespace ndn { -namespace nrd { -namespace tests { - -BOOST_AUTO_TEST_SUITE(TestRib) - -BOOST_AUTO_TEST_CASE(Basic) -{ - Rib rib; - - PrefixRegOptions options1; - options1.setName("/hello/world"); - options1.setFlags(tlv::nrd::NDN_FORW_CHILD_INHERIT | tlv::nrd::NDN_FORW_CAPTURE); - options1.setCost(10); - options1.setExpirationPeriod(time::milliseconds(1500)); - options1.setFaceId(1); - - rib.insert(options1); - BOOST_CHECK_EQUAL(rib.size(), 1); - - PrefixRegOptions options2; - options2.setName("/hello/world"); - options2.setFlags(tlv::nrd::NDN_FORW_CHILD_INHERIT); - options2.setExpirationPeriod(time::seconds(0)); - options2.setFaceId(1); - options2.setCost(100); - - rib.insert(options2); - BOOST_CHECK_EQUAL(rib.size(), 1); - - options2.setFaceId(2); - rib.insert(options2); - BOOST_CHECK_EQUAL(rib.size(), 2); - - options2.setName("/foo/bar"); - rib.insert(options2); - BOOST_CHECK_EQUAL(rib.size(), 3); - - rib.erase(options2); - BOOST_CHECK_EQUAL(rib.size(), 2); - - options2.setName("/hello/world"); - rib.erase(options2); - BOOST_CHECK_EQUAL(rib.size(), 1); - - BOOST_CHECK(rib.find(options2) == rib.end()); - BOOST_CHECK(rib.find(options1) != rib.end()); - - rib.erase(options1); - BOOST_CHECK(rib.empty()); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // namespace tests -} // namespace nrd -} // namespace ndn diff --git a/rib/waf b/rib/waf deleted file mode 100755 index 78a44f3717b3a81469a2c3400e18d429b14c9c18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91336 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

{ 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�#>#_AbZ39nEFOQ-6nuYT* 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 J3L`u{Jqm}E@>>7^ diff --git a/rib/wscript b/rib/wscript deleted file mode 100644 index 1a7f82a3..00000000 --- a/rib/wscript +++ /dev/null @@ -1,71 +0,0 @@ -# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*- - -from waflib import Logs - -def options(opt): - opt.load('compiler_cxx gnu_dirs') - opt.load('flags boost doxygen coverage', tooldir=['.waf-tools']) - - nrdopt = opt.add_option_group('NRD Options') - nrdopt.add_option('--debug', action='store_true', default=False, - dest='debug', - help='''Compile library debugging mode without all optimizations (-O0)''') - nrdopt.add_option('--with-tests', action='store_true', - default=False, dest='with_tests', help='''Build unit tests''') - -def configure(conf): - conf.load("compiler_cxx gnu_dirs boost flags") - - conf.check_cfg(package='libndn-cpp-dev', args=['--cflags', '--libs'], - uselib_store='NDN_CPP', mandatory=True) - - boost_libs = 'system' - if conf.options.with_tests: - conf.env['WITH_TESTS'] = 1 - conf.define('WITH_TESTS', 1); - boost_libs += ' unit_test_framework' - - conf.check_boost(lib=boost_libs) - if conf.env.BOOST_VERSION_NUMBER < 104800: - Logs.error("Minimum required boost version is 1.48") - Logs.error("Please upgrade your distribution or install custom boost libraries" + - " (http://redmine.named-data.net/projects/nfd/wiki/Boost_FAQ)") - return - - # conf.load('coverage') - - # try: - # conf.load('doxygen') - # except: - # pass - - conf.define('DEFAULT_CONFIG_FILE', '%s/ndn/nrd.conf' % conf.env['SYSCONFDIR']) - conf.write_config_header('src/config.hpp') - -def build (bld): - bld( - features='cxx', - name='nrd-objects', - source=bld.path.ant_glob('src/*.cpp', - excl='src/main.cpp'), - use='NDN_CPP BOOST', - ) - - bld.program( - target='nrd', - source='src/main.cpp', - use='nrd-objects' - ) - - # Unit tests - if bld.env['WITH_TESTS']: - unit_tests = bld.program( - target='unit-tests', - features='cxx cxxprogram', - source=bld.path.ant_glob(['tests/**/*.cpp']), - use='nrd-objects', - includes=['.', 'src'], - install_prefix=None, - ) - - bld.install_files('${SYSCONFDIR}/ndn', 'nrd.conf.sample') diff --git a/tests/rib/rib.cpp b/tests/rib/rib.cpp new file mode 100644 index 00000000..fd5777b2 --- /dev/null +++ b/tests/rib/rib.cpp @@ -0,0 +1,86 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/** + * Copyright (c) 2014 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 . + **/ + +#include "rib/rib.hpp" + +#include "tests/test-common.hpp" + +namespace nfd { +namespace rib { +namespace tests { + +BOOST_FIXTURE_TEST_SUITE(Rib, nfd::tests::BaseFixture) + +BOOST_AUTO_TEST_CASE(Basic) +{ + rib::Rib rib; + + PrefixRegOptions options1; + options1.setName("/hello/world"); + options1.setFlags(tlv::nrd::NDN_FORW_CHILD_INHERIT | tlv::nrd::NDN_FORW_CAPTURE); + options1.setCost(10); + options1.setExpirationPeriod(time::milliseconds(1500)); + options1.setFaceId(1); + + rib.insert(options1); + BOOST_CHECK_EQUAL(rib.size(), 1); + + PrefixRegOptions options2; + options2.setName("/hello/world"); + options2.setFlags(tlv::nrd::NDN_FORW_CHILD_INHERIT); + options2.setExpirationPeriod(time::seconds(0)); + options2.setFaceId(1); + options2.setCost(100); + + rib.insert(options2); + BOOST_CHECK_EQUAL(rib.size(), 1); + + options2.setFaceId(2); + rib.insert(options2); + BOOST_CHECK_EQUAL(rib.size(), 2); + + options2.setName("/foo/bar"); + rib.insert(options2); + BOOST_CHECK_EQUAL(rib.size(), 3); + + rib.erase(options2); + BOOST_CHECK_EQUAL(rib.size(), 2); + + options2.setName("/hello/world"); + rib.erase(options2); + BOOST_CHECK_EQUAL(rib.size(), 1); + + BOOST_CHECK(rib.find(options2) == rib.end()); + BOOST_CHECK(rib.find(options1) != rib.end()); + + rib.erase(options1); + BOOST_CHECK(rib.empty()); +} + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace tests +} // namespace rib +} // namespace nfd diff --git a/tests/wscript b/tests/wscript index 423e5e3d..bf569134 100644 --- a/tests/wscript +++ b/tests/wscript @@ -64,6 +64,15 @@ def build(bld): if bld.env['HAVE_UNIX_SOCKETS']: unit_tests_nfd.source += bld.path.ant_glob('daemon/face/unix-*.cpp') + unit_tests_rib = bld.program( + target='../unit-tests-rib', + features='cxx cxxprogram', + source=bld.path.ant_glob(['rib/**/*.cpp']), + use='rib-objects unit-tests-main', + includes=['.'], + install_prefix=None, + ) + # Other tests (e.g., stress tests that can be enabled even if unit tests are disabled) if bld.env['WITH_TESTS'] or bld.env['WITH_OTHER_TESTS']: bld.recurse("other") diff --git a/wscript b/wscript index 39ea4b1a..a476ee4f 100644 --- a/wscript +++ b/wscript @@ -138,6 +138,21 @@ def build(bld): use='daemon-objects', ) + rib_objects = bld( + target='rib-objects', + name='rib-objects', + features='cxx', + source=bld.path.ant_glob(['rib/**/*.cpp'], + excl=['rib/main.cpp']), + use='core-objects', + ) + + bld(target='bin/nrd', + features='cxx cxxprogram', + source='rib/main.cpp', + use='rib-objects', + ) + for app in bld.path.ant_glob('tools/*.cpp'): bld(features=['cxx', 'cxxprogram'], target='bin/%s' % (str(app.change_ext(''))), @@ -153,6 +168,9 @@ def build(bld): install_path="${SYSCONFDIR}/ndn", IF_HAVE_LIBPCAP="" if bld.env['HAVE_LIBPCAP'] else "; ") + # @todo Merge with nfd.conf + bld.install_files('${SYSCONFDIR}/ndn', 'nrd.conf.sample') + bld(features='subst', source='tools/nfd-status-http-server.py', target='nfd-status-http-server',