blob: 922722fed0073e787714610da5663f6f97bb5f74 [file] [log] [blame]
/* -*- 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