mgmt: Dispatcher
Change-Id: I92b3dc9daae75abac9d791632b6a0bec111b4573
refs: #2107
diff --git a/src/mgmt/control-parameters.hpp b/src/mgmt/control-parameters.hpp
new file mode 100644
index 0000000..3a17091
--- /dev/null
+++ b/src/mgmt/control-parameters.hpp
@@ -0,0 +1,49 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, 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/>.
+ */
+
+#ifndef NDN_MGMT_CONTROL_PARAMETERS_HPP
+#define NDN_MGMT_CONTROL_PARAMETERS_HPP
+
+#include "../encoding/block.hpp"
+
+namespace ndn {
+namespace mgmt {
+
+/** \brief base class for a struct that contains ControlCommand parameters
+ */
+class ControlParameters
+{
+public:
+ virtual void
+ wireDecode(const Block& wire) = 0;
+
+ virtual Block
+ wireEncode() const = 0;
+};
+
+} // namespace mgmt
+} // namespace ndn
+
+#endif // NDN_MGMT_CONTROL_PARAMETERS_HPP
diff --git a/src/mgmt/control-response.cpp b/src/mgmt/control-response.cpp
new file mode 100644
index 0000000..9c66f2a
--- /dev/null
+++ b/src/mgmt/control-response.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, 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 "control-response.hpp"
+#include "../encoding/block-helpers.hpp"
+#include "../encoding/tlv-nfd.hpp"
+
+namespace ndn {
+namespace mgmt {
+
+// BOOST_CONCEPT_ASSERT((boost::EqualityComparable<ControlResponse>));
+BOOST_CONCEPT_ASSERT((WireEncodable<ControlResponse>));
+BOOST_CONCEPT_ASSERT((WireDecodable<ControlResponse>));
+static_assert(std::is_base_of<tlv::Error, ControlResponse::Error>::value,
+ "ControlResponse::Error must inherit from tlv::Error");
+
+ControlResponse::ControlResponse()
+ : m_code(200)
+{
+}
+
+ControlResponse::ControlResponse(uint32_t code, const std::string& text)
+ : m_code(code)
+ , m_text(text)
+{
+}
+
+ControlResponse::ControlResponse(const Block& block)
+{
+ wireDecode(block);
+}
+
+const Block&
+ControlResponse::wireEncode() const
+{
+ if (m_wire.hasWire())
+ return m_wire;
+
+ m_wire = Block(tlv::nfd::ControlResponse);
+ m_wire.push_back(nonNegativeIntegerBlock(tlv::nfd::StatusCode, m_code));
+
+ m_wire.push_back(dataBlock(tlv::nfd::StatusText, m_text.c_str(), m_text.size()));
+
+ if (m_body.hasWire()) {
+ m_wire.push_back(m_body);
+ }
+
+ m_wire.encode();
+ return m_wire;
+}
+
+void
+ControlResponse::wireDecode(const Block& wire)
+{
+ m_wire = wire;
+ m_wire.parse();
+
+ if (m_wire.type() != tlv::nfd::ControlResponse)
+ throw Error("Requested decoding of ControlResponse, but Block is of different type");
+
+ Block::element_const_iterator val = m_wire.elements_begin();
+ if (val == m_wire.elements_end() || val->type() != tlv::nfd::StatusCode) {
+ throw Error("Incorrect ControlResponse format (StatusCode missing or not the first item)");
+ }
+
+ m_code = readNonNegativeInteger(*val);
+ ++val;
+
+ if (val == m_wire.elements_end() || val->type() != tlv::nfd::StatusText) {
+ throw Error("Incorrect ControlResponse format (StatusText missing or not the second item)");
+ }
+ m_text.assign(reinterpret_cast<const char*>(val->value()), val->value_size());
+ ++val;
+
+ if (val != m_wire.elements_end())
+ m_body = *val;
+ else
+ m_body = Block();
+}
+
+std::ostream&
+operator<<(std::ostream& os, const ControlResponse& response)
+{
+ os << response.getCode() << " " << response.getText();
+ return os;
+}
+
+} // namespace mgmt
+} // namespace ndn
diff --git a/src/mgmt/control-response.hpp b/src/mgmt/control-response.hpp
new file mode 100644
index 0000000..a107940
--- /dev/null
+++ b/src/mgmt/control-response.hpp
@@ -0,0 +1,137 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, 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/>.
+ */
+
+#ifndef NDN_MGMT_CONTROL_RESPONSE_HPP
+#define NDN_MGMT_CONTROL_RESPONSE_HPP
+
+#include "../encoding/block.hpp"
+
+namespace ndn {
+namespace mgmt {
+
+/** \brief ControlCommand response
+ */
+class ControlResponse
+{
+public:
+ class Error : public tlv::Error
+ {
+ public:
+ explicit
+ Error(const std::string& what)
+ : tlv::Error(what)
+ {
+ }
+ };
+
+ ControlResponse();
+
+ ControlResponse(uint32_t code, const std::string& text);
+
+ explicit
+ ControlResponse(const Block& block);
+
+ uint32_t
+ getCode() const;
+
+ ControlResponse&
+ setCode(uint32_t code);
+
+ const std::string&
+ getText() const;
+
+ ControlResponse&
+ setText(const std::string& text);
+
+ const Block&
+ getBody() const;
+
+ ControlResponse&
+ setBody(const Block& body);
+
+ const Block&
+ wireEncode() const;
+
+ void
+ wireDecode(const Block& block);
+
+protected:
+ uint32_t m_code;
+ std::string m_text;
+ Block m_body;
+
+ mutable Block m_wire;
+};
+
+inline uint32_t
+ControlResponse::getCode() const
+{
+ return m_code;
+}
+
+inline ControlResponse&
+ControlResponse::setCode(uint32_t code)
+{
+ m_code = code;
+ m_wire.reset();
+ return *this;
+}
+
+inline const std::string&
+ControlResponse::getText() const
+{
+ return m_text;
+}
+
+inline ControlResponse&
+ControlResponse::setText(const std::string& text)
+{
+ m_text = text;
+ m_wire.reset();
+ return *this;
+}
+
+inline const Block&
+ControlResponse::getBody() const
+{
+ return m_body;
+}
+
+inline ControlResponse&
+ControlResponse::setBody(const Block& body)
+{
+ m_body = body;
+ m_body.encode(); // will do nothing if already encoded
+ m_wire.reset();
+ return *this;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const ControlResponse& response);
+
+} // namespace mgmt
+} // namespace ndn
+
+#endif // NDN_MGMT_CONTRO_RESPONSE_HPP
diff --git a/src/mgmt/dispatcher.cpp b/src/mgmt/dispatcher.cpp
new file mode 100644
index 0000000..b43334b
--- /dev/null
+++ b/src/mgmt/dispatcher.cpp
@@ -0,0 +1,310 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, 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 "dispatcher.hpp"
+
+#include <algorithm>
+
+// #define NDN_CXX_MGMT_DISPATCHER_ENABLE_LOGGING
+
+namespace ndn {
+namespace mgmt {
+
+Authorization
+makeAcceptAllAuthorization()
+{
+ return [] (const Name& prefix,
+ const Interest& interest,
+ const ControlParameters* params,
+ AcceptContinuation accept,
+ RejectContinuation reject) {
+ accept("");
+ };
+}
+
+Dispatcher::Dispatcher(Face& face, security::KeyChain& keyChain,
+ const security::SigningInfo& signingInfo)
+ : m_face(face)
+ , m_keyChain(keyChain)
+ , m_signingInfo(signingInfo)
+{
+}
+
+Dispatcher::~Dispatcher()
+{
+ std::vector<Name> topPrefixNames;
+
+ std::transform(m_topLevelPrefixes.begin(),
+ m_topLevelPrefixes.end(),
+ std::back_inserter(topPrefixNames),
+ [] (const std::unordered_map<Name, TopPrefixEntry>::value_type& entry) {
+ return entry.second.topPrefix;
+ });
+
+ for (auto&& name : topPrefixNames) {
+ removeTopPrefix(name);
+ }
+}
+
+void
+Dispatcher::addTopPrefix(const Name& prefix,
+ bool wantRegister,
+ const security::SigningInfo& signingInfo)
+{
+ bool hasOverlap = std::any_of(m_topLevelPrefixes.begin(),
+ m_topLevelPrefixes.end(),
+ [&] (const std::unordered_map<Name, TopPrefixEntry>::value_type& x) {
+ return x.first.isPrefixOf(prefix) || prefix.isPrefixOf(x.first);
+ });
+ if (hasOverlap) {
+ BOOST_THROW_EXCEPTION(std::out_of_range("Top-level Prefixes overlapped"));
+ }
+
+ TopPrefixEntry& topPrefixEntry = m_topLevelPrefixes[prefix];;
+ topPrefixEntry.topPrefix = prefix;
+ topPrefixEntry.wantRegister = wantRegister;
+
+ if (wantRegister) {
+ RegisterPrefixFailureCallback failure = [] (const Name& name, const std::string& reason) {
+ BOOST_THROW_EXCEPTION(std::runtime_error(reason));
+ };
+ topPrefixEntry.registerPrefixId =
+ m_face.registerPrefix(prefix, bind([]{}), failure, signingInfo);
+ }
+
+ for (auto&& entry : m_handlers) {
+ Name fullPrefix = prefix;
+ fullPrefix.append(entry.first);
+
+ const InterestFilterId* interestFilterId =
+ m_face.setInterestFilter(fullPrefix, std::bind(entry.second, prefix, _2));
+
+ topPrefixEntry.interestFilters.push_back(interestFilterId);
+ }
+}
+
+void
+Dispatcher::removeTopPrefix(const Name& prefix)
+{
+ auto it = m_topLevelPrefixes.find(prefix);
+ if (it == m_topLevelPrefixes.end()) {
+ return;
+ }
+
+ const TopPrefixEntry& topPrefixEntry = it->second;
+ if (topPrefixEntry.wantRegister) {
+ m_face.unregisterPrefix(topPrefixEntry.registerPrefixId, bind([]{}), bind([]{}));
+ }
+
+ for (auto&& filter : topPrefixEntry.interestFilters) {
+ m_face.unsetInterestFilter(filter);
+ }
+
+ m_topLevelPrefixes.erase(it);
+}
+
+bool
+Dispatcher::isOverlappedWithOthers(const PartialName& relPrefix)
+{
+ bool hasOverlapWithHandlers =
+ std::any_of(m_handlers.begin(), m_handlers.end(),
+ [&] (const HandlerMap::value_type& entry) {
+ return entry.first.isPrefixOf(relPrefix) || relPrefix.isPrefixOf(entry.first);
+ });
+ bool hasOverlapWithStreams =
+ std::any_of(m_streams.begin(), m_streams.end(),
+ [&] (const std::unordered_map<PartialName, uint64_t>::value_type& entry) {
+ return entry.first.isPrefixOf(relPrefix) || relPrefix.isPrefixOf(entry.first);
+ });
+
+ return hasOverlapWithHandlers || hasOverlapWithStreams;
+}
+
+void
+Dispatcher::afterAuthorizationRejected(RejectReply act, const Interest& interest)
+{
+ if (act == RejectReply::STATUS403) {
+ sendControlResponse(ControlResponse(403, "authorization rejected"), interest);
+ }
+}
+
+void
+Dispatcher::sendData(const Name& dataName, const Block& content,
+ const MetaInfo& metaInfo)
+{
+ shared_ptr<Data> data = make_shared<Data>(dataName);
+ data->setContent(content).setMetaInfo(metaInfo);
+
+ m_keyChain.sign(*data, m_signingInfo);
+
+ try {
+ m_face.put(*data);
+ }
+ catch (Face::Error& e) {
+#ifdef NDN_CXX_MGMT_DISPATCHER_ENABLE_LOGGING
+ std::clog << e.what() << std::endl;
+#endif // NDN_CXX_MGMT_DISPATCHER_ENABLE_LOGGING.
+ }
+}
+
+void
+Dispatcher::processControlCommandInterest(const Name& prefix,
+ const Name& relPrefix,
+ const Interest& interest,
+ const ControlParametersParser& parser,
+ const Authorization& authorization,
+ const AuthorizationAcceptedCallback& accepted,
+ const AuthorizationRejectedCallback& rejected)
+{
+ // /<prefix>/<relPrefix>/<parameters>
+ size_t parametersLoc = prefix.size() + relPrefix.size();
+ const name::Component& pc = interest.getName().get(parametersLoc);
+
+ shared_ptr<ControlParameters> parameters;
+ try {
+ parameters = parser(pc);
+ }
+ catch (tlv::Error& e) {
+ return;
+ }
+
+ AcceptContinuation accept = bind(accepted, _1, prefix, interest, parameters.get());
+ RejectContinuation reject = bind(rejected, _1, interest);
+ authorization(prefix, interest, parameters.get(), accept, reject);
+}
+
+void
+Dispatcher::processAuthorizedControlCommandInterest(const std::string& requester,
+ const Name& prefix,
+ const Interest& interest,
+ const ControlParameters* parameters,
+ const ValidateParameters& validateParams,
+ const ControlCommandHandler& handler)
+{
+ if (validateParams(*parameters)) {
+ handler(prefix, interest, *parameters,
+ bind(&Dispatcher::sendControlResponse, this, _1, interest, false));
+ }
+ else {
+ sendControlResponse(ControlResponse(400, "failed in validating parameters"), interest);
+ }
+}
+
+void
+Dispatcher::sendControlResponse(const ControlResponse& resp, const Interest& interest,
+ bool isNack/*= false*/)
+{
+ MetaInfo info;
+ if (isNack) {
+ info.setType(tlv::ContentType_Nack);
+ }
+
+ sendData(interest.getName(), resp.wireEncode(), info);
+}
+
+void
+Dispatcher::addStatusDataset(const PartialName& relPrefix,
+ Authorization authorization,
+ StatusDatasetHandler handler)
+{
+ if (!m_topLevelPrefixes.empty()) {
+ BOOST_THROW_EXCEPTION(std::domain_error("one or more top-level prefix has been added"));
+ }
+
+ if (isOverlappedWithOthers(relPrefix)) {
+ BOOST_THROW_EXCEPTION(std::out_of_range("relPrefix overlapped"));
+ }
+
+ AuthorizationAcceptedCallback accepted =
+ bind(&Dispatcher::processAuthorizedStatusDatasetInterest, this,
+ _1, _2, _3, handler);
+ AuthorizationRejectedCallback rejected =
+ bind(&Dispatcher::afterAuthorizationRejected, this, _1, _2);
+ m_handlers[relPrefix] = bind(&Dispatcher::processStatusDatasetInterest, this,
+ _1, _2, authorization, accepted, rejected);
+}
+
+void
+Dispatcher::processStatusDatasetInterest(const Name& prefix,
+ const Interest& interest,
+ const Authorization& authorization,
+ const AuthorizationAcceptedCallback& accepted,
+ const AuthorizationRejectedCallback& rejected)
+{
+ const Name& interestName = interest.getName();
+ bool endsWithVersionOrSegment = interestName.size() >= 1 &&
+ (interestName[-1].isVersion() || interestName[-1].isSegment());
+ if (endsWithVersionOrSegment) {
+ return;
+ }
+
+ AcceptContinuation accept = bind(accepted, _1, prefix, interest, nullptr);
+ RejectContinuation reject = bind(rejected, _1, interest);
+ authorization(prefix, interest, nullptr, accept, reject);
+}
+
+void
+Dispatcher::processAuthorizedStatusDatasetInterest(const std::string& requester,
+ const Name& prefix,
+ const Interest& interest,
+ const StatusDatasetHandler& handler)
+{
+ StatusDatasetContext context(interest, bind(&Dispatcher::sendData, this, _1, _2, _3));
+ handler(prefix, interest, context);
+}
+
+PostNotification
+Dispatcher::addNotificationStream(const PartialName& relPrefix)
+{
+ if (!m_topLevelPrefixes.empty()) {
+ throw std::domain_error("one or more top-level prefix has been added");
+ }
+
+ if (isOverlappedWithOthers(relPrefix)) {
+ throw std::out_of_range("relPrefix overlaps with another relPrefix");
+ }
+
+ m_streams[relPrefix] = 0;
+ return bind(&Dispatcher::postNotification, this, _1, relPrefix);
+}
+
+void
+Dispatcher::postNotification(const Block& notification, const PartialName& relPrefix)
+{
+ if (m_topLevelPrefixes.empty() || m_topLevelPrefixes.size() > 1) {
+#ifdef NDN_CXX_MGMT_DISPATCHER_ENABLE_LOGGING
+ std::clog << "no top-level prefix or too many top-level prefixes" << std::endl;
+#endif // NDN_CXX_MGMT_DISPATCHER_ENABLE_LOGGING.
+ return;
+ }
+
+ Name streamName(m_topLevelPrefixes.begin()->second.topPrefix);
+ streamName.append(relPrefix);
+ streamName.appendSequenceNumber(m_streams[streamName]++);
+ sendData(streamName, notification, MetaInfo());
+}
+
+} // namespace mgmt
+} // namespace ndn
diff --git a/src/mgmt/dispatcher.hpp b/src/mgmt/dispatcher.hpp
new file mode 100644
index 0000000..922722f
--- /dev/null
+++ b/src/mgmt/dispatcher.hpp
@@ -0,0 +1,451 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, 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/>.
+ */
+
+#ifndef NDN_MGMT_DISPATCHER_HPP
+#define NDN_MGMT_DISPATCHER_HPP
+
+#include "../face.hpp"
+#include "../security/key-chain.hpp"
+#include "../encoding/block.hpp"
+#include "control-response.hpp"
+#include "control-parameters.hpp"
+#include "status-dataset-context.hpp"
+
+#include <unordered_map>
+
+namespace ndn {
+namespace mgmt {
+
+// ---- AUTHORIZATION ----
+
+/** \brief a function to be called if authorization is successful
+ * \param requester a string that indicates the requester, whose semantics is determined by
+ * the Authorization function; this value is intended for logging only,
+ * and should not affect how the request is processed
+ */
+typedef std::function<void(const std::string& requester)> AcceptContinuation;
+
+/** \brief indicate how to reply in case authorization is rejected
+ */
+enum class RejectReply {
+ /** \brief do not reply
+ */
+ SILENT,
+ /** \brief reply with a ControlResponse where StatusCode is 403
+ */
+ STATUS403
+};
+
+/** \brief a function to be called if authorization is rejected
+ */
+typedef std::function<void(RejectReply act)> RejectContinuation;
+
+/** \brief a function that performs authorization
+ * \param prefix top-level prefix, e.g., "/localhost/nfd";
+ * This argument can be inspected to allow Interests only under a subset of
+ * top-level prefixes (e.g., allow "/localhost/nfd" only),
+ * or to use different trust model regarding to the prefix.
+ * \param interest incoming Interest
+ * \param params parsed ControlParameters for ControlCommand, otherwise nullptr;
+ * This is guaranteed to be not-null and have correct type for the command,
+ * but may not be valid (e.g., can have missing fields).
+ *
+ * Either accept or reject must be called after authorization completes.
+ */
+typedef std::function<void(const Name& prefix, const Interest& interest,
+ const ControlParameters* params,
+ AcceptContinuation accept,
+ RejectContinuation reject)> Authorization;
+
+/** \return an Authorization that accepts all Interests, with empty string as requester
+ */
+Authorization
+makeAcceptAllAuthorization();
+
+// ---- CONTROL COMMAND ----
+
+/** \brief a function to validate input ControlParameters
+ * \param params parsed ControlParameters;
+ * This is guaranteed to have correct type for the command.
+ */
+typedef std::function<bool(const ControlParameters& params)> ValidateParameters;
+
+/** \brief a function to be called after ControlCommandHandler completes
+ * \param resp the response to be sent to requester
+ */
+typedef std::function<void(const ControlResponse& resp)> CommandContinuation;
+
+/** \brief a function to handle an authorized ControlCommand
+ * \param prefix top-level prefix, e.g., "/localhost/nfd";
+ * \param interest incoming Interest
+ * \param params parsed ControlParameters;
+ * This is guaranteed to have correct type for the command,
+ * and is valid (e.g., has all required fields).
+ */
+typedef std::function<void(const Name& prefix, const Interest& interest,
+ const ControlParameters& params,
+ CommandContinuation done)> ControlCommandHandler;
+
+
+/** \brief a function to handle a StatusDataset request
+ * \param prefix top-level prefix, e.g., "/localhost/nfd";
+ * \param interest incoming Interest; its Name doesn't contain version and segment components
+ *
+ * This function can generate zero or more blocks and pass them to \p append,
+ * and must call \p end upon completion.
+ */
+typedef std::function<void(const Name& prefix, const Interest& interest,
+ StatusDatasetContext& context)> StatusDatasetHandler;
+
+//---- NOTIFICATION STREAM ----
+
+/** \brief a function to post a notification
+ */
+typedef std::function<void(const Block& notification)> PostNotification;
+
+// ---- DISPATCHER ----
+
+/** \brief represents a dispatcher on server side of NFD Management protocol
+ */
+class Dispatcher : noncopyable
+{
+ class Error : public std::runtime_error
+ {
+ public:
+ explicit
+ Error(const std::string& what)
+ : std::runtime_error(what)
+ {
+ }
+ };
+
+public:
+ /** \brief constructor
+ * \param face the Face on which the dispatcher operates
+ * \param keyChain a KeyChain to sign Data
+ * \param signingInfo signing parameters to sign Data with \p keyChain
+ */
+ Dispatcher(Face& face, security::KeyChain& keyChain,
+ const security::SigningInfo& signingInfo = security::SigningInfo());
+
+ virtual
+ ~Dispatcher();
+
+ /** \brief add a top-level prefix
+ * \param prefix a top-level prefix, e.g., "/localhost/nfd"
+ * \param wantRegister whether prefix registration should be performed through the Face
+ * \param signingInfo signing parameters to sign the prefix registration command
+ * \throw std::out_of_range \p prefix overlaps with an existing top-level prefix
+ *
+ * Procedure for adding a top-level prefix:
+ * 1. if the new top-level prefix overlaps with an existing top-level prefix
+ * (one top-level prefix is a prefix of another top-level prefix), throw std::domain_error
+ * 2. if wantRegister is true, invoke face.registerPrefix for the top-level prefix;
+ * the returned RegisteredPrefixId shall be recorded internally, indexed by the top-level
+ * prefix
+ * 3. foreach relPrefix from ControlCommands and StatusDatasets,
+ * join the top-level prefix with the relPrefix to obtain the full prefix,
+ * and invoke non-registering overload of face.setInterestFilter,
+ * with the InterestHandler set to an appropriate private method to handle incoming Interests
+ * for the ControlCommand or StatusDataset;
+ * the returned InterestFilterId shall be recorded internally, indexed by the top-level
+ * prefix
+ */
+ void
+ addTopPrefix(const Name& prefix, bool wantRegister = true,
+ const security::SigningInfo& signingInfo = security::SigningInfo());
+
+ /** \brief remove a top-level prefix
+ * \param prefix a top-level prefix, e.g., "/localhost/nfd"
+ *
+ * Procedure for removing a top-level prefix:
+ * 1. if the top-level prefix has not been added, abort these steps
+ * 2. if the top-level prefix has been added with wantRegister,
+ * invoke face.unregisterPrefix with the RegisteredPrefixId
+ * 3. foreach InterestFilterId recorded during addTopPrefix,
+ * invoke face.unsetInterestFilter with the InterestFilterId
+ */
+ void
+ removeTopPrefix(const Name& prefix);
+
+public: // ControlCommand
+ /** \brief register a ControlCommand
+ * \tparam CP subclass of ControlParameters used by this command
+ * \param relPrefix a prefix for this command, e.g., "faces/create";
+ * relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be
+ * non-overlapping
+ * (no relPrefix is a prefix of another relPrefix)
+ * \pre no top-level prefix has been added
+ * \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix
+ * \throw std::domain_error one or more top-level prefix has been added
+ *
+ * Procedure for processing a ControlCommand:
+ * 1. extract the NameComponent containing ControlParameters (the component after relPrefix),
+ * and parse ControlParameters into type CP; if parsing fails, abort these steps
+ * 2. perform authorization; if authorization is rejected,
+ * perform the RejectReply action, and abort these steps
+ * 3. validate ControlParameters; if validation fails,
+ * make ControlResponse with StatusCode 400, and go to step 5
+ * 4. invoke handler, wait until CommandContinuation is called
+ * 5. encode the ControlResponse into one Data packet
+ * 6. sign the Data packet
+ * 7. if the Data packet is too large, abort these steps and log an error
+ * 8. send the signed Data packet
+ */
+ template<typename CP>
+ void
+ addControlCommand(const PartialName& relPrefix,
+ Authorization authorization,
+ ValidateParameters validateParams,
+ ControlCommandHandler handler);
+
+public: // StatusDataset
+ /** \brief register a StatusDataset or a prefix under which StatusDatasets can be requested
+ * \param relPrefix a prefix for this dataset, e.g., "faces/list";
+ * relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be
+ * non-overlapping
+ * (no relPrefix is a prefix of another relPrefix)
+ * \param authorization should set identity to Name() if the dataset is public
+ * \pre no top-level prefix has been added
+ * \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix
+ * \throw std::domain_error one or more top-level prefix has been added
+ *
+ * The payload of the returned status dataset data packet is at most half of the maximum
+ * data packet size.
+ *
+ * Procedure for processing a StatusDataset request:
+ * 1. if the request Interest contains version or segment components, abort these steps;
+ * note: the request may contain more components after relPrefix, e.g., a query condition
+ * 2. perform authorization; if authorization is rejected,
+ * perform the RejectReply action, and abort these steps
+ * 3. invoke handler, store blocks passed to StatusDatasetAppend calls in a buffer,
+ * wait until StatusDatasetEnd is called
+ * 4. allocate a version
+ * 5. segment the buffer into one or more segments under the allocated version,
+ * such that the Data packets will not become too large after signing
+ * 6. set FinalBlockId on at least the last segment
+ * 7. sign the Data packets
+ * 8. send the signed Data packets
+ *
+ * As an optimization, a Data packet may be sent as soon as enough octets have been collected
+ * through StatusDatasetAppend calls.
+ */
+ void
+ addStatusDataset(const PartialName& relPrefix,
+ Authorization authorization,
+ StatusDatasetHandler handler);
+
+public: // NotificationStream
+ /** \brief register a NotificationStream
+ * \param relPrefix a prefix for this notification stream, e.g., "faces/events";
+ * relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be
+ * non-overlapping
+ * (no relPrefix is a prefix of another relPrefix)
+ * \return a function into which notifications can be posted
+ * \pre no top-level prefix has been added
+ * \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix
+ * \throw std::domain_error one or more top-level prefix has been added
+ *
+ * Procedure for posting a notification:
+ * 1. if no top-level prefix has been added, or more than one top-level prefixes have been
+ * added,
+ * abort these steps and log an error
+ * 2. assign the next sequence number to the notification
+ * 3. place the notification block into one Data packet under the sole top-level prefix
+ * 4. sign the Data packet
+ * 5. if the Data packet is too large, abort these steps and log an error
+ * 6. send the signed Data packet
+ */
+ PostNotification
+ addNotificationStream(const PartialName& relPrefix);
+
+private:
+ typedef std::function<void(const Name& prefix,
+ const Interest& interest)> InterestHandler;
+
+ typedef std::function<void(const std::string& requester,
+ const Name& prefix,
+ const Interest& interest,
+ const ControlParameters*)> AuthorizationAcceptedCallback;
+
+ typedef std::function<void(RejectReply act,
+ const Interest& interest)> AuthorizationRejectedCallback;
+
+ /**
+ * @brief the parser of extracting control parameters from name component.
+ *
+ * @param component name component that may encode control parameters.
+ * @return a shared pointer to the extracted control parameters.
+ * @throw tlv::Error if the NameComponent cannot be parsed as ControlParameters
+ */
+ typedef std::function<shared_ptr<ControlParameters>(const name::Component& component)>
+ ControlParametersParser;
+
+ bool
+ isOverlappedWithOthers(const PartialName& relPrefix);
+
+ /**
+ * @brief process unauthorized request
+ *
+ * @param act action to reply
+ * @param interest the incoming Interest
+ */
+ void
+ afterAuthorizationRejected(RejectReply act, const Interest& interest);
+
+ void
+ sendData(const Name& dataName, const Block& content,
+ const MetaInfo& metaInfo);
+
+ /**
+ * @brief process the control-command Interest before authorization.
+ *
+ * @param prefix the top-level prefix
+ * @param relPrefix the relative prefix
+ * @param interest the incoming Interest
+ * @param parser to extract control parameters from the \p interest
+ * @param authorization to process validation on this command
+ * @param accepted the callback for successful authorization
+ * @param rejected the callback for failed authorization
+ */
+ void
+ processControlCommandInterest(const Name& prefix,
+ const Name& relPrefix,
+ const Interest& interest,
+ const ControlParametersParser& parser,
+ const Authorization& authorization,
+ const AuthorizationAcceptedCallback& accepted,
+ const AuthorizationRejectedCallback& rejected);
+
+ /**
+ * @brief process the authorized control-command.
+ *
+ * @param requester the requester
+ * @param prefix the top-level prefix
+ * @param interest the incoming Interest
+ * @param parameters control parameters of this command
+ * @param validate to validate control parameters
+ * @param handler to process this command
+ */
+ void
+ processAuthorizedControlCommandInterest(const std::string& requester,
+ const Name& prefix,
+ const Interest& interest,
+ const ControlParameters* parameters,
+ const ValidateParameters& validate,
+ const ControlCommandHandler& handler);
+
+ void
+ sendControlResponse(const ControlResponse& resp, const Interest& interest, bool isNack = false);
+
+ /**
+ * @brief process the status-dataset Interest before authorization.
+ *
+ * @param prefix the top-level prefix
+ * @param interest the incoming Interest
+ * @param authorization to process verification
+ * @param accepted callback for successful authorization
+ * @param rejected callback for failed authorization
+ */
+ void
+ processStatusDatasetInterest(const Name& prefix,
+ const Interest& interest,
+ const Authorization& authorization,
+ const AuthorizationAcceptedCallback& accepted,
+ const AuthorizationRejectedCallback& rejected);
+
+ /**
+ * @brief process the authorized status-dataset request
+ *
+ * @param requester the requester
+ * @param prefix the top-level prefix
+ * @param interest the incoming Interest
+ * @param handler to process this request
+ */
+ void
+ processAuthorizedStatusDatasetInterest(const std::string& requester,
+ const Name& prefix,
+ const Interest& interest,
+ const StatusDatasetHandler& handler);
+
+ void
+ postNotification(const Block& notification, const PartialName& relPrefix);
+
+private:
+ struct TopPrefixEntry
+ {
+ Name topPrefix;
+ bool wantRegister;
+ const ndn::RegisteredPrefixId* registerPrefixId;
+ std::vector<const ndn::InterestFilterId*> interestFilters;
+ };
+ std::unordered_map<Name, TopPrefixEntry> m_topLevelPrefixes;
+
+ Face& m_face;
+ security::KeyChain& m_keyChain;
+ security::SigningInfo m_signingInfo;
+
+ typedef std::unordered_map<PartialName, InterestHandler> HandlerMap;
+ typedef HandlerMap::iterator HandlerMapIt;
+ HandlerMap m_handlers;
+
+ // NotificationStream name => next sequence number
+ std::unordered_map<Name, uint64_t> m_streams;
+};
+
+template<typename CP>
+void
+Dispatcher::addControlCommand(const PartialName& relPrefix,
+ Authorization authorization,
+ ValidateParameters validateParams,
+ ControlCommandHandler handler)
+{
+ if (!m_topLevelPrefixes.empty()) {
+ throw std::domain_error("one or more top-level prefix has been added");
+ }
+
+ if (isOverlappedWithOthers(relPrefix)) {
+ throw std::out_of_range("relPrefix overlaps with another relPrefix");
+ }
+
+ ControlParametersParser parser =
+ [] (const name::Component& component) -> shared_ptr<ControlParameters> {
+ return make_shared<CP>(component.blockFromValue());
+ };
+
+ AuthorizationAcceptedCallback accepted =
+ bind(&Dispatcher::processAuthorizedControlCommandInterest, this,
+ _1, _2, _3, _4, validateParams, handler);
+
+ AuthorizationRejectedCallback rejected =
+ bind(&Dispatcher::afterAuthorizationRejected, this, _1, _2);
+
+ m_handlers[relPrefix] = bind(&Dispatcher::processControlCommandInterest, this,
+ _1, relPrefix, _2, parser, authorization, accepted, rejected);
+}
+
+} // namespace mgmt
+} // namespace ndn
+#endif // NDN_MGMT_DISPATCHER_HPP
diff --git a/src/mgmt/status-dataset-context.cpp b/src/mgmt/status-dataset-context.cpp
new file mode 100644
index 0000000..1d386ef
--- /dev/null
+++ b/src/mgmt/status-dataset-context.cpp
@@ -0,0 +1,139 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, 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 "status-dataset-context.hpp"
+
+namespace ndn {
+namespace mgmt {
+
+const time::milliseconds DEFAULT_STATUS_DATASET_FRESHNESS_PERIOD = time::milliseconds(1000);
+
+const Name&
+StatusDatasetContext::getPrefix() const
+{
+ return m_prefix;
+}
+
+StatusDatasetContext&
+StatusDatasetContext::setPrefix(const Name& prefix)
+{
+ if (!m_interest.getName().isPrefixOf(prefix)) {
+ BOOST_THROW_EXCEPTION(std::invalid_argument("prefix does not start with Interest Name"));
+ }
+
+ if (m_state != State::INITIAL) {
+ BOOST_THROW_EXCEPTION(std::domain_error("state is not in INITIAL"));
+ }
+
+ m_prefix = prefix;
+
+ if (!m_prefix[-1].isVersion()) {
+ m_prefix.appendVersion();
+ }
+
+ return *this;
+}
+
+const time::milliseconds&
+StatusDatasetContext::getExpiry() const
+{
+ return m_expiry;
+}
+
+StatusDatasetContext&
+StatusDatasetContext::setExpiry(const time::milliseconds& expiry)
+{
+ m_expiry = expiry;
+ return *this;
+}
+
+void
+StatusDatasetContext::append(const Block& block)
+{
+ if (m_state == State::FINALIZED) {
+ BOOST_THROW_EXCEPTION(std::domain_error("state is in FINALIZED"));
+ }
+
+ m_state = State::RESPONDED;
+
+ size_t nBytesLeft = block.size();
+
+ while (nBytesLeft > 0) {
+ size_t nBytesAppend = std::min(nBytesLeft,
+ (ndn::MAX_NDN_PACKET_SIZE >> 1) - m_buffer->size());
+ m_buffer->appendByteArray(block.wire() + (block.size() - nBytesLeft), nBytesAppend);
+ nBytesLeft -= nBytesAppend;
+
+ if (nBytesLeft > 0) {
+ const Block& content = makeBinaryBlock(tlv::Content, m_buffer->buf(), m_buffer->size());
+ m_dataSender(Name(m_prefix).appendSegment(m_segmentNo++), content,
+ MetaInfo().setFreshnessPeriod(m_expiry));
+
+ m_buffer = std::make_shared<EncodingBuffer>();
+ }
+ }
+}
+
+void
+StatusDatasetContext::end()
+{
+ if (m_state == State::FINALIZED) {
+ BOOST_THROW_EXCEPTION(std::domain_error("state is in FINALIZED"));
+ }
+
+ m_state = State::FINALIZED;
+
+ auto dataName = Name(m_prefix).appendSegment(m_segmentNo++);
+ m_dataSender(dataName, makeBinaryBlock(tlv::Content, m_buffer->buf(), m_buffer->size()),
+ MetaInfo().setFreshnessPeriod(m_expiry).setFinalBlockId(dataName[-1]));
+}
+
+void
+StatusDatasetContext::reject(const ControlResponse& resp /*= a ControlResponse with 400*/)
+{
+ if (m_state != State::INITIAL) {
+ BOOST_THROW_EXCEPTION(std::domain_error("state is in REPONSED or FINALIZED"));
+ }
+
+ m_state = State::FINALIZED;
+
+ m_dataSender(m_interest.getName(), resp.wireEncode(),
+ MetaInfo().setType(tlv::ContentType_Nack));
+}
+
+StatusDatasetContext::StatusDatasetContext(const Interest& interest,
+ const DataSender& dataSender)
+ : m_interest(interest)
+ , m_dataSender(dataSender)
+ , m_expiry(DEFAULT_STATUS_DATASET_FRESHNESS_PERIOD)
+ , m_buffer(make_shared<EncodingBuffer>())
+ , m_segmentNo(0)
+ , m_state(State::INITIAL)
+{
+ setPrefix(interest.getName());
+}
+
+} // namespace mgmt
+} // namespace ndn
diff --git a/src/mgmt/status-dataset-context.hpp b/src/mgmt/status-dataset-context.hpp
new file mode 100644
index 0000000..196a296
--- /dev/null
+++ b/src/mgmt/status-dataset-context.hpp
@@ -0,0 +1,125 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, 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/>.
+ */
+
+#ifndef NDN_MGMT_STATUS_DATASET_CONTEXT_HPP
+#define NDN_MGMT_STATUS_DATASET_CONTEXT_HPP
+
+#include "../interest.hpp"
+#include "../data.hpp"
+#include "../util/time.hpp"
+#include "../encoding/encoding-buffer.hpp"
+#include "control-response.hpp"
+
+namespace ndn {
+namespace mgmt {
+
+class StatusDatasetContext
+{
+public:
+ /** \return prefix of Data packets, with version component but without segment component
+ */
+ const Name&
+ getPrefix() const;
+
+ /** \brief change prefix of Data packets
+ * \param prefix the prefix; it must start with Interest Name, may contain version component,
+ * but must not contain segment component
+ * \throw std::invalid_argument prefix does not start with Interest Name
+ * \throw std::domain_error append, end, or reject has been invoked
+ *
+ * StatusDatasetHandler may change the prefix of Data packets with this method,
+ * before sending any response.
+ * The version component is optional, and will be generated from current timestamp when omitted.
+ */
+ StatusDatasetContext&
+ setPrefix(const Name& prefix);
+
+ /** \return expiration duration for this dataset response
+ */
+ const time::milliseconds&
+ getExpiry() const;
+
+ /** \brief set expiration duration
+ *
+ * The response will be cached for the specified duration.
+ * Incoming Interest that matches a cached response will be satisfied with that response,
+ * without invoking StatusDatasetHandler again.
+ */
+ StatusDatasetContext&
+ setExpiry(const time::milliseconds& expiry);
+
+ /** \brief append a Block to the response
+ * \throw std::domain_error end or reject has been invoked
+ */
+ void
+ append(const Block& block);
+
+ /** \brief end the response successfully after appending zero or more blocks
+ * \throw std::domain_error reject has been invoked
+ */
+ void
+ end();
+
+ /** \brief declare the non-existence of a response
+ * \throw std::domain_error append or end has been invoked
+ *
+ * This should be invoked when the incoming Interest is malformed.
+ * A producer-generated NACK will be returned to requester.
+ *
+ * \param content Content of producer-generated NACK
+ */
+ void
+ reject(const ControlResponse& resp = ControlResponse().setCode(400));
+
+NDN_CXX_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+ typedef std::function<void(const Name& dataName, const Block& content,
+ const MetaInfo& metaInfo)> DataSender;
+
+ StatusDatasetContext(const Interest& interest, const DataSender& dataSender);
+
+private:
+ friend class Dispatcher;
+
+ const Interest& m_interest;
+ DataSender m_dataSender;
+ Name m_prefix;
+ time::milliseconds m_expiry;
+
+NDN_CXX_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+ shared_ptr<EncodingBuffer> m_buffer;
+ uint64_t m_segmentNo;
+
+ enum class State {
+ INITIAL, ///< none of .append, .end, .reject has been invoked
+ RESPONDED, ///< .append has been invoked
+ FINALIZED ///< .end or .reject has been invoked
+ };
+ State m_state;
+};
+
+} // namespace mgmt
+} // namespace ndn
+
+#endif // NDN_MGMT_STATUS_DATASET_CONTEXT_HPP