mgmt: Dispatcher

Change-Id: I92b3dc9daae75abac9d791632b6a0bec111b4573
refs: #2107
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