| /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ |
| /** |
| * Copyright (c) 2013-2017 Regents of the University of California. |
| * |
| * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). |
| * |
| * ndn-cxx library is free software: you can redistribute it and/or modify it under the |
| * terms of the GNU Lesser General Public License as published by the Free Software |
| * Foundation, either version 3 of the License, or (at your option) any later version. |
| * |
| * ndn-cxx library 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 Lesser General Public License for more details. |
| * |
| * You should have received copies of the GNU General Public License and GNU Lesser |
| * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see |
| * <http://www.gnu.org/licenses/>. |
| * |
| * See AUTHORS.md for complete list of ndn-cxx authors and contributors. |
| */ |
| |
| #ifndef NDN_MGMT_DISPATCHER_HPP |
| #define NDN_MGMT_DISPATCHER_HPP |
| |
| #include "../face.hpp" |
| #include "../security/key-chain.hpp" |
| #include "../encoding/block.hpp" |
| #include "../util/in-memory-storage-fifo.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, |
| const AcceptContinuation& accept, |
| const 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, |
| const 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 |
| * \param imsCapacity capacity of the internal InMemoryStorage used by dispatcher |
| */ |
| Dispatcher(Face& face, security::v1::KeyChain& keyChain, |
| const security::SigningInfo& signingInfo = security::SigningInfo(), |
| size_t imsCapacity = 256); |
| |
| 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) |
| * \param authorization Callback to authorize the incoming commands |
| * \param validateParams Callback to validate parameters of the incoming commands |
| * \param handler Callback to handle the commands |
| * \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, |
| const Authorization& authorization, |
| const ValidateParameters& validateParams, |
| const 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 |
| * \param handler Callback to process the incoming dataset requests |
| * \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, |
| const Authorization& authorization, |
| const 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 shared_ptr<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); |
| |
| /** |
| * @brief query Data the in-memory storage by a given Interest |
| * |
| * if the query fails, invoke @p missContinuation to process @p interest. |
| * |
| * @param prefix the top-level prefix |
| * @param interest the request |
| * @param missContinuation the handler of request when the query fails |
| */ |
| void |
| queryStorage(const Name& prefix, const Interest& interest, const InterestHandler& missContinuation); |
| |
| enum class SendDestination { |
| NONE = 0, |
| FACE = 1, |
| IMS = 2, |
| FACE_AND_IMS = 3 |
| }; |
| |
| /** |
| * @brief send data to the face or in-memory storage |
| * |
| * create a data packet with the given @p dataName, @p content, and @p metaInfo, |
| * set its FreshnessPeriod to DEFAULT_FRESHNESS_PERIOD, and then send it out through the face and/or |
| * insert it into the in-memory storage as specified in @p option. |
| * |
| * if it's toward the in-memory storage, set its CachePolicy to NO_CACHE and limit |
| * its FreshnessPeriod in the storage as @p imsFresh |
| * |
| * @param dataName the name of this piece of data |
| * @param content the content of this piece of data |
| * @param metaInfo some meta information of this piece of data |
| * @param destination where to send this piece of data |
| * @param imsFresh freshness period of this piece of data in in-memory storage |
| */ |
| void |
| sendData(const Name& dataName, const Block& content, const MetaInfo& metaInfo, |
| SendDestination destination, time::milliseconds imsFresh); |
| |
| /** |
| * @brief send out a data packt through the face |
| * |
| * @param data the data packet to insert |
| */ |
| void |
| sendOnFace(const Data& data); |
| |
| /** |
| * @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 shared_ptr<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); |
| |
| /** |
| * @brief send a segment of StatusDataset |
| * |
| * @param dataName the name of this piece of data |
| * @param content the content of this piece of data |
| * @param imsFresh the freshness period of this piece of data in the in-memory storage |
| * @param isFinalBlock indicates whether this piece of data is the final block |
| */ |
| void |
| sendStatusDatasetSegment(const Name& dataName, const Block& content, |
| time::milliseconds imsFresh, bool isFinalBlock); |
| |
| 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::v1::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; |
| |
| NDN_CXX_PUBLIC_WITH_TESTS_ELSE_PRIVATE: |
| util::InMemoryStorageFifo m_storage; |
| }; |
| |
| template<typename CP> |
| void |
| Dispatcher::addControlCommand(const PartialName& relPrefix, |
| const Authorization& authorization, |
| const ValidateParameters& validateParams, |
| const ControlCommandHandler& 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 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 |