catalog: implement trust model

Change-Id: I28a9ead71357eb180316cd1707138518b5fb8b88
refs: #3020
diff --git a/.jenkins.d/10-ndn-cxx.sh b/.jenkins.d/10-ndn-cxx.sh
index 3ca0932..089ef14 100755
--- a/.jenkins.d/10-ndn-cxx.sh
+++ b/.jenkins.d/10-ndn-cxx.sh
@@ -23,9 +23,11 @@
 
 pushd ndn-cxx >/dev/null
 
-./waf configure -j1 --color=yes --without-osx-keychain --disable-static --enable-shared
+./waf configure -j1 --color=yes --without-osx-keychain
 ./waf -j1 --color=yes
 sudo ./waf install -j1 --color=yes
+(echo -e '/usr/local/lib\n/usr/local/lib64' | sudo tee /etc/ld.so.conf.d/ndn-cxx.conf) || true
+sudo ldconfig || true
 
 popd >/dev/null
 popd >/dev/null
diff --git a/.jenkins.d/11-chronoSync.sh b/.jenkins.d/11-chronoSync.sh
index 50318d4..4b0ee2b 100755
--- a/.jenkins.d/11-chronoSync.sh
+++ b/.jenkins.d/11-chronoSync.sh
@@ -27,6 +27,7 @@
 /usr/local/lib32/pkgconfig:\
 /usr/local/lib64/pkgconfig
 
+sudo ldconfig || true
 ./waf configure -j1 --color=yes
 ./waf -j1 --color=yes
 sudo ./waf install -j1 --color=yes
diff --git a/catalog.conf.sample.in b/catalog.conf.sample.in
index 64d2558..11b11d0 100644
--- a/catalog.conf.sample.in
+++ b/catalog.conf.sample.in
@@ -31,10 +31,32 @@
   ; ; Set the identity that signs published data
   ; signingId ndn:/cmip5/test/publish/identity
 
-  ; ; The published_file_security section contains the rules for the adapter to verify the
+  ; ; The security section contains the rules for the adapter to verify the
   ; ; published files indeed come from a valid publisher.
-  ; published_file_security
+  ; security
   ; {
+  ;   rule
+  ;   {
+  ;     id "NDN Hierarchy Test Rule"
+  ;     for data                             ; rule for Data (to validate NDN certificates)
+  ;     filter
+  ;     {
+  ;       type name                          ; condition on data name
+  ;       regex ^(<>*)$
+  ;     }
+  ;     checker
+  ;     {
+  ;       type hierarchical                  ; the certificate name of the signing key and
+  ;                                          ; the data name must follow the hierarchical model
+  ;       sig-type rsa-sha256                ; data must have a rsa-sha256 signature
+  ;     }
+  ;   }
+  ;   trust-anchor
+  ;   {
+  ;     type file
+  ;     file-name /directory/to/the/root.ndncert ; the file name, by default this file should be
+  ;                                              ; in same folder as this config file.
+  ;   }
   ; }
 
   ; The database section contains settings of database
diff --git a/catalog/src/publish/publish-adapter.hpp b/catalog/src/publish/publish-adapter.hpp
index 9d29434..492bcff 100644
--- a/catalog/src/publish/publish-adapter.hpp
+++ b/catalog/src/publish/publish-adapter.hpp
@@ -21,6 +21,7 @@
 
 #include "util/catalog-adapter.hpp"
 #include "util/mysql-util.hpp"
+#include <mysql/mysql.h>
 
 #include <json/reader.h>
 #include <json/value.h>
@@ -31,8 +32,7 @@
 #include <ndn-cxx/interest-filter.hpp>
 #include <ndn-cxx/name.hpp>
 #include <ndn-cxx/security/key-chain.hpp>
-#include <ndn-cxx/security/validator.hpp>
-#include "mysql/mysql.h"
+#include <ndn-cxx/security/validator-config.hpp>
 
 #include <memory>
 #include <string>
@@ -41,6 +41,7 @@
 
 namespace atmos {
 namespace publish {
+
 /**
  * PublishAdapter handles the Publish usecases for the catalog
  */
@@ -107,13 +108,23 @@
   void
   setFilters();
 
+   /**
+   * Function to validate publication changes against the trust model, which is, all file
+   * names must be under the publisher's prefix. This function should be called by a callback
+   * function invoked by validator
+   *
+   * @param data: received data from the publisher
+   */
+  bool
+  validatePublicationChanges(const std::shared_ptr<const ndn::Data>& data);
+
 protected:
   typedef std::unordered_map<ndn::Name, const ndn::RegisteredPrefixId*> RegisteredPrefixList;
   // Prefix for ChronoSync
   ndn::Name m_syncPrefix;
   // Handle to the Catalog's database
   std::shared_ptr<DatabaseHandler> m_databaseHandler;
-  std::shared_ptr<ndn::Validator> m_validaor;
+  std::unique_ptr<ndn::ValidatorConfig> m_publishValidator;
   RegisteredPrefixList m_registeredPrefixList;
 };
 
@@ -130,13 +141,14 @@
 PublishAdapter<DatabaseHandler>::setFilters()
 {
   ndn::Name publishPrefix = ndn::Name(m_prefix).append("publish");
-  m_registeredPrefixList[publishPrefix] = m_face->setInterestFilter(publishPrefix,
-                                                                    bind(&publish::PublishAdapter<DatabaseHandler>::onPublishInterest,
-                                                                         this, _1, _2),
-                            bind(&publish::PublishAdapter<DatabaseHandler>::onRegisterSuccess,
-                                 this, _1),
-                            bind(&publish::PublishAdapter<DatabaseHandler>::onRegisterFailure,
-                                 this, _1, _2));
+  m_registeredPrefixList[publishPrefix] =
+    m_face->setInterestFilter(publishPrefix,
+                              bind(&PublishAdapter<DatabaseHandler>::onPublishInterest,
+                                   this, _1, _2),
+                              bind(&publish::PublishAdapter<DatabaseHandler>::onRegisterSuccess,
+                                   this, _1),
+                              bind(&publish::PublishAdapter<DatabaseHandler>::onRegisterFailure,
+                                   this, _1, _2));
 }
 
 template <typename DatabaseHandler>
@@ -184,9 +196,12 @@
                                 " in \"publish\" section");
       }
     }
-
-    // @todo: parse the published_file_security section
-
+    else if (item->first == "security") {
+      // when use, the validator must specify the callback func to handle the validated data
+      // it should be called when the Data packet that contains the published file names is received
+      m_publishValidator.reset(new ndn::ValidatorConfig(m_face.get()));
+      m_publishValidator->load(item->second, filename);
+    }
     else if (item->first == "database") {
       const util::ConfigSection& databaseSection = item->second;
       for (auto subItem = databaseSection.begin();
@@ -279,7 +294,41 @@
 PublishAdapter<DatabaseHandler>::onPublishedData(const ndn::Interest& interest,
                                                  const ndn::Data& data)
 {
-  // @todo handle publishing the data
+  // @todo handle data publication
+}
+
+template<typename DatabaseHandler>
+bool
+PublishAdapter<DatabaseHandler>::validatePublicationChanges(const std::shared_ptr<const ndn::Data>& data)
+{
+  // The data name must be "/<publisher-prefix>/<nonce>"
+  // the prefix is the data name removes the last component
+  ndn::Name publisherPrefix = data->getName().getPrefix(-1);
+
+  const std::string payload(reinterpret_cast<const char*>(data->getContent().value()),
+                            data->getContent().value_size());
+  Json::Value parsedFromString;
+  Json::Reader reader;
+  if (!reader.parse(payload, parsedFromString)) {
+    // parse error, log events
+    std::cout << "Cannot parse the published data " << data->getName() << " into Json" << std::endl;
+    return false;
+  }
+
+  // validate added files...
+  for (size_t i = 0; i < parsedFromString["add"].size(); i++) {
+    if (!publisherPrefix.isPrefixOf(
+          ndn::Name(parsedFromString["add"][static_cast<int>(i)].asString())))
+      return false;
+  }
+
+  // validate removed files ...
+  for (size_t i = 0; i < parsedFromString["remove"].size(); i++) {
+    if (!publisherPrefix.isPrefixOf(
+          ndn::Name(parsedFromString["remove"][static_cast<int>(i)].asString())))
+      return false;
+  }
+  return true;
 }
 
 } // namespace publish
diff --git a/catalog/tests/unit-tests/publish/test-publish-adapter.cpp b/catalog/tests/unit-tests/publish/test-publish-adapter.cpp
new file mode 100644
index 0000000..7d6a894
--- /dev/null
+++ b/catalog/tests/unit-tests/publish/test-publish-adapter.cpp
@@ -0,0 +1,235 @@
+/** NDN-Atmos: Cataloging Service for distributed data originally developed
+ *  for atmospheric science data
+ *  Copyright (C) 2015 Colorado State University
+ *
+ *  NDN-Atmos 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.
+ *
+ *  NDN-Atmos 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 NDN-Atmos.  If not, see <http://www.gnu.org/licenses/>.
+**/
+
+#include "publish/publish-adapter.hpp"
+#include "boost-test.hpp"
+#include "../../unit-test-time-fixture.hpp"
+#include "util/config-file.hpp"
+
+#include <boost/mpl/list.hpp>
+#include <boost/thread.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+#include <boost/property_tree/info_parser.hpp>
+
+namespace atmos{
+namespace tests{
+  using ndn::util::DummyClientFace;
+  using ndn::util::makeDummyClientFace;
+
+  class PublishAdapterTest : public publish::PublishAdapter<std::string>
+  {
+  public:
+    PublishAdapterTest(const std::shared_ptr<ndn::util::DummyClientFace>& face,
+                       const std::shared_ptr<ndn::KeyChain>& keyChain)
+      : publish::PublishAdapter<std::string>(face, keyChain)
+    {
+    }
+
+    virtual
+    ~PublishAdapterTest()
+    {
+    }
+
+    const ndn::Name
+    getPrefix()
+    {
+      return m_prefix;
+    }
+
+    const ndn::Name
+    getSigningId()
+    {
+      return m_signingId;
+    }
+
+    const ndn::Name
+    getSyncPrefix()
+    {
+      return m_syncPrefix;
+    }
+
+    void
+    configAdapter(const util::ConfigSection& section,
+                  const ndn::Name& prefix)
+    {
+      onConfig(section, false, std::string("test.txt"), prefix);
+    }
+
+    bool
+    testValidatePublicationChanges(const std::shared_ptr<const ndn::Data>& data)
+    {
+      return validatePublicationChanges(data);
+    }
+  };
+
+  class PublishAdapterFixture : public UnitTestTimeFixture
+  {
+  public:
+    PublishAdapterFixture()
+      : face(makeDummyClientFace(io))
+      , keyChain(new ndn::KeyChain())
+      , publishAdapterTest1(face, keyChain)
+      , publishAdapterTest2(face, keyChain)
+    {
+    }
+
+    virtual
+    ~PublishAdapterFixture()
+    {
+    }
+
+  protected:
+    void
+    initializePublishAdapterTest1()
+    {
+      util::ConfigSection section;
+      try {
+        std::stringstream ss;
+        ss << "database                  \
+             {                           \
+              dbServer localhost         \
+              dbName testdb              \
+              dbUser testuser            \
+              dbPasswd testpwd           \
+             }                           \
+             sync                        \
+             {                           \
+              prefix ndn:/ndn/broadcast1 \
+             }";
+        boost::property_tree::read_info(ss, section);
+      }
+      catch (boost::property_tree::info_parser_error &e) {
+        std::cout << "Failed to read config file " << e.what() << std::endl;
+      }
+      publishAdapterTest1.configAdapter(section, ndn::Name("/test"));
+    }
+
+    void
+    initializePublishAdapterTest2()
+    {
+      util::ConfigSection section;
+      try {
+        std::stringstream ss;
+        ss << "\
+             signingId /prefix/signingId \
+             database                    \
+             {                           \
+              dbServer localhost         \
+              dbName testdb              \
+              dbUser testuser            \
+              dbPasswd testpwd           \
+             }                           \
+             sync                        \
+             {                           \
+             }";
+        boost::property_tree::read_info(ss, section);
+      }
+      catch (boost::property_tree::info_parser_error &e) {
+        std::cout << "Failed to read config file " << e.what() << std::endl;;
+      }
+      publishAdapterTest2.configAdapter(section, ndn::Name("/test"));
+    }
+
+  protected:
+    std::shared_ptr<DummyClientFace> face;
+    std::shared_ptr<ndn::KeyChain> keyChain;
+    PublishAdapterTest publishAdapterTest1;
+    PublishAdapterTest publishAdapterTest2;
+  };
+
+  BOOST_FIXTURE_TEST_SUITE(PublishAdapterTestSuite, PublishAdapterFixture)
+
+  BOOST_AUTO_TEST_CASE(BasicPublishAdapterTest1)
+  {
+    BOOST_CHECK(publishAdapterTest1.getPrefix() == ndn::Name());
+    BOOST_CHECK(publishAdapterTest1.getSigningId() == ndn::Name());
+    BOOST_CHECK(publishAdapterTest1.getSyncPrefix() == ndn::Name());
+  }
+
+  BOOST_AUTO_TEST_CASE(BasicPublishAdapterTest2)
+  {
+    initializePublishAdapterTest1();
+    BOOST_CHECK(publishAdapterTest1.getPrefix() == ndn::Name("/test"));
+    BOOST_CHECK(publishAdapterTest1.getSigningId() == ndn::Name());
+    BOOST_CHECK(publishAdapterTest1.getSyncPrefix() == ndn::Name("ndn:/ndn/broadcast1"));
+
+    initializePublishAdapterTest2();
+    BOOST_CHECK(publishAdapterTest2.getPrefix() == ndn::Name("/test"));
+    BOOST_CHECK(publishAdapterTest2.getSigningId() == ndn::Name("/prefix/signingId"));
+    BOOST_CHECK(publishAdapterTest2.getSyncPrefix() ==
+                ndn::Name("ndn:/ndn-atmos/broadcast/chronosync"));
+  }
+
+  BOOST_AUTO_TEST_CASE(PublishAdapterValidateDataTestSuccess)
+  {
+    ndn::Name dataName("/test/publisher/12345"); // data name must be prefix+nonce
+    Json::Value testJson;
+    testJson["add"][0] = "/test/publisher/1";
+    testJson["add"][1] = "/test/publisher/2";
+    testJson["remove"][0] = "/test/publisher/5";
+
+    Json::FastWriter fastWriter;
+    const std::string jsonMessage = fastWriter.write(testJson);
+    const char* payload = jsonMessage.c_str();
+    size_t payLoadLength = jsonMessage.size() + 1;
+
+    std::shared_ptr<ndn::Data> data = std::make_shared<ndn::Data>(dataName);
+    data->setContent(reinterpret_cast<const uint8_t*>(payload), payLoadLength);
+    data->setFreshnessPeriod(ndn::time::milliseconds(10000));
+
+    BOOST_CHECK_EQUAL(true, publishAdapterTest1.testValidatePublicationChanges(data));
+
+    ndn::Name dataName2("/"); // short data name
+    Json::Value testJson2;
+    testJson2["add"][0] = "/test/publisher2/1";
+    testJson2["remove"][0] = "/test/publisher/1/2/3";
+
+    const std::string jsonMessage2 = fastWriter.write(testJson2);
+    const char* payload2 = jsonMessage2.c_str();
+    size_t payLoadLength2 = jsonMessage2.size() + 1;
+
+    std::shared_ptr<ndn::Data> data2 = std::make_shared<ndn::Data>(dataName2);
+    data2->setContent(reinterpret_cast<const uint8_t*>(payload2), payLoadLength2);
+    data2->setFreshnessPeriod(ndn::time::milliseconds(10000));
+    BOOST_CHECK_EQUAL(true, publishAdapterTest1.testValidatePublicationChanges(data2));
+  }
+
+  BOOST_AUTO_TEST_CASE(PublishAdapterValidateDataTestFail)
+  {
+    ndn::Name dataName1("test/publisher2/12345"); // data name must be prefix+nonce
+    Json::Value testJson1;
+    testJson1["add"][0] = "/test/publisher2/1";
+    testJson1["remove"][0] = "/test/publisher/1/2/3";
+    testJson1["remove"][1] = "/test/publisher2/4";
+
+    Json::FastWriter fastWriter;
+    const std::string jsonMessage1 = fastWriter.write(testJson1);
+    const char* payload1 = jsonMessage1.c_str();
+    size_t payLoadLength1 = jsonMessage1.size() + 1;
+
+    std::shared_ptr<ndn::Data> data1 = std::make_shared<ndn::Data>(dataName1);
+    data1->setContent(reinterpret_cast<const uint8_t*>(payload1), payLoadLength1);
+    data1->setFreshnessPeriod(ndn::time::milliseconds(10000));
+
+    BOOST_CHECK_EQUAL(false, publishAdapterTest1.testValidatePublicationChanges(data1));
+  }
+
+  BOOST_AUTO_TEST_SUITE_END()
+
+}//tests
+}//atmos
diff --git a/wscript b/wscript
index d348f4a..28c51be 100644
--- a/wscript
+++ b/wscript
@@ -90,7 +90,7 @@
         features='cxx',
         source=bld.path.ant_glob(['catalog/src/**/*.cpp'],
                                  excl=['catalog/src/main.cpp']),
-        use='NDN_CXX BOOST JSON MYSQL',
+        use='NDN_CXX BOOST JSON MYSQL SYNC',
         includes='catalog/src .',
         export_includes='catalog/src .'
     )