rib: make Service globally accessible via get() static function

Change-Id: I2101fb81a613d301acbce7f26e062caa692c5e29
Refs: #4683
diff --git a/core/global-io.hpp b/core/global-io.hpp
index f9abccf..ab0e17c 100644
--- a/core/global-io.hpp
+++ b/core/global-io.hpp
@@ -42,10 +42,10 @@
 void
 runOnRibIoService(const std::function<void()>& f);
 
-#ifdef WITH_TESTS
 boost::asio::io_service&
 getRibIoService();
 
+#ifdef WITH_TESTS
 /** \brief delete the global io_service instance
  *
  *  It will be recreated at the next invocation of getGlobalIoService.
diff --git a/rib/service.cpp b/rib/service.cpp
index dce806f..de103b4 100644
--- a/rib/service.cpp
+++ b/rib/service.cpp
@@ -37,22 +37,50 @@
 
 static const std::string INTERNAL_CONFIG = "internal://nfd.conf";
 
+Service* Service::s_instance = nullptr;
+
 Service::Service(const std::string& configFile, ndn::KeyChain& keyChain)
   : m_configFile(configFile)
   , m_keyChain(keyChain)
 {
+  if (s_instance != nullptr) {
+    BOOST_THROW_EXCEPTION(std::logic_error("RIB service cannot be instantiated more than once"));
+  }
+  if (&getGlobalIoService() != &getRibIoService()) {
+    BOOST_THROW_EXCEPTION(std::logic_error("RIB service must run on RIB thread"));
+  }
+  s_instance = this;
 }
 
 Service::Service(const ConfigSection& config, ndn::KeyChain& keyChain)
   : m_configSection(config)
   , m_keyChain(keyChain)
 {
+  if (s_instance != nullptr) {
+    BOOST_THROW_EXCEPTION(std::logic_error("RIB service cannot be instantiated more than once"));
+  }
+  if (&getGlobalIoService() != &getRibIoService()) {
+    BOOST_THROW_EXCEPTION(std::logic_error("RIB service must run on RIB thread"));
+  }
+  s_instance = this;
 }
 
-// It is necessary to explicitly define the destructor, because some member variables
-// (e.g., unique_ptr<RibManager>) are forward-declared, but implicitly declared destructor
-// requires complete types for all members when instantiated.
-Service::~Service() = default;
+Service::~Service()
+{
+  s_instance = nullptr;
+}
+
+Service&
+Service::get()
+{
+  if (s_instance == nullptr) {
+    BOOST_THROW_EXCEPTION(std::logic_error("RIB service is not instantiated"));
+  }
+  if (&getGlobalIoService() != &getRibIoService()) {
+    BOOST_THROW_EXCEPTION(std::logic_error("Must get RIB service on RIB thread"));
+  }
+  return *s_instance;
+}
 
 void
 Service::initialize()
diff --git a/rib/service.hpp b/rib/service.hpp
index 35223dc..6c01f3e 100644
--- a/rib/service.hpp
+++ b/rib/service.hpp
@@ -40,6 +40,8 @@
 
 /**
  * \brief initializes and executes NFD-RIB service thread
+ *
+ * Only one instance of this class can be created at any time
  */
 class Service : noncopyable
 {
@@ -58,6 +60,8 @@
    * \brief create NFD-RIB service
    * \param configFile absolute or relative path of configuration file
    * \param keyChain the KeyChain
+   * \throw std::logic_error Instance of rib::Service has been already constructed
+   * \throw std::logic_error Instance of rib::Service is not constructed on RIB thread
    */
   Service(const std::string& configFile, ndn::KeyChain& keyChain);
 
@@ -68,6 +72,8 @@
    * \note This constructor overload is more appropriate for integrated environments,
    *       such as NS-3 or android. Error messages related to configuration file
    *       will use "internal://nfd.conf" as configuration filename.
+   * \throw std::logic_error Instance of rib::Service has been already constructed
+   * \throw std::logic_error Instance of rib::Service is not constructed on RIB thread
    */
   Service(const ConfigSection& config, ndn::KeyChain& keyChain);
 
@@ -84,6 +90,14 @@
   void
   initialize();
 
+  /**
+   * \brief Get a reference to the only instance of this class
+   * \throw std::logic_error No instance has been constructed
+   * \throw std::logic_error This function is invoked on a thread other than the RIB thread
+   */
+  static Service&
+  get();
+
 private:
   /**
    * \brief Look into the config file and construct appropriate transport to communicate with NFD
@@ -93,6 +107,8 @@
   getLocalNfdTransport();
 
 private:
+  static Service* s_instance;
+
   std::string m_configFile;
   ConfigSection m_configSection;
 
diff --git a/tests/rib/rib-service.t.cpp b/tests/rib/rib-service.t.cpp
new file mode 100644
index 0000000..5bfe5b9
--- /dev/null
+++ b/tests/rib/rib-service.t.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rib/service.hpp"
+#include "tests/rib-io-fixture.hpp"
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace rib {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(TestService, nfd::tests::RibIoFixture)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  ndn::KeyChain ribKeyChain;
+  const std::string configFile;
+
+  BOOST_CHECK_THROW(Service::get(), std::logic_error);
+  BOOST_CHECK_THROW(Service(configFile, ribKeyChain), std::logic_error);
+
+  runOnRibIoService([&] {
+    {
+      BOOST_CHECK_THROW(Service::get(), std::logic_error);
+      Service ribService(configFile, ribKeyChain);
+      BOOST_CHECK_EQUAL(&ribService, &Service::get());
+    }
+    BOOST_CHECK_THROW(Service::get(), std::logic_error);
+    Service ribService(configFile, ribKeyChain);
+    BOOST_CHECK_THROW(Service(configFile, ribKeyChain), std::logic_error);
+  });
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestRibService
+
+} // namespace tests
+} // namespace rib
+} // namespace nfd