Last active
March 9, 2016 22:16
-
-
Save yoursunny/e3c520a590af86a0bb37 to your computer and use it in GitHub Desktop.
NFD management dispatcher http://redmine.named-data.net/issues/2200
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
* | |
* The ControlResponse shall be enclosed in a BLOB Data for ControlCommand, | |
* and in an producer-generated NACK for StatusDataset. | |
*/ | |
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, eg. "/localhost/nfd"; | |
* This argument can be inspected to allow Interests only under a subset of | |
* top-level prefixes (eg. 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 (eg. 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(); | |
// ---- ControlCommand ---- | |
/** \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; | |
}; | |
/** \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 ControlCommand response | |
* | |
* This structure is also used as the Content in a producer-generated NACK response. | |
*/ | |
class ControlResponse | |
{ | |
// (move from original ndn::nfd::ControlResponse) | |
}; | |
/** \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, eg. "/localhost/nfd"; | |
* \param interest incoming Interest | |
* \param params parsed ControlParameters; | |
* This is guaranteed to have correct type for the command, | |
* and is valid (eg. has all required fields). | |
*/ | |
typedef std::function<void(const Name& prefix, const Interest& interest, | |
const ControlParameters& params, | |
const CommandContinuation& done)> ControlCommandHandler; | |
// ---- StatusDataset ---- | |
class StatusDatasetContext : noncopyable | |
{ | |
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. | |
*/ | |
void | |
setPrefix(const Name& prefix); | |
/** \return expiration duration for this dataset response | |
*/ | |
const time::milliseconds& | |
getExpiry() const; | |
// note: initial expiration duration is 1000ms | |
/** \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. | |
*/ | |
void | |
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 = /* a ControlResponse with code 400 */); | |
private: | |
friend class Dispatcher; | |
}; | |
// note: no additional public members should be added; | |
// private members can be added as necessary, which is accessible from Dispatcher which is declared as a friend | |
// note: before #2182, setExpiry should be setting the FreshnessPeriod field of all outgoing Data. | |
/** \brief a function to handle a StatusDataset request | |
* \param prefix top-level prefix, eg. "/localhost/nfd"; | |
* \param interest incoming Interest; its Name doesn't contain version and segment components | |
* \param context a collection of functions to respond to the request | |
* | |
* This function can generate zero or more blocks and pass them to \p context.append, | |
* and must call \p context.end upon completion. | |
* Alternatively, this function may reject the request by calling \p context.reject. | |
*/ | |
typedef std::function<void(const Name& prefix, const Interest& interest, | |
StatusDatasetContext& context)> StatusDatasetHandler; | |
// ---- NotificationStream ---- | |
/** \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 | |
{ | |
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::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, eg. "/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()); | |
// note: SigningInfo taken in constructor shouldn't be used for the prefix registration command, | |
// because the Face may have a different KeyChain | |
/** \brief remove a top-level prefix | |
* \param prefix a top-level prefix, eg. "/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, eg. "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, | |
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, eg. "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 | |
* | |
* Procedure for processing a StatusDataset request: | |
* 1. query the internal InMemoryStorage; | |
* if a match is found, send the Data with CachePolicy=NoCache | |
* 2. if the request Interest contains version or segment components, abort these steps; | |
* note: the request may contain more components after relPrefix, eg. a query condition | |
* 3. perform authorization; if authorization is rejected, | |
* perform the RejectReply action, and abort these steps | |
* 4. construct StatusDatasetContext | |
* 5. invoke handler, wait until either StatusDatasetContext::end or | |
* StatusDatasetContext::reject is called | |
* 6. if StatusDatasetContext::reject has been called, create a producer-generated NACK, | |
* sign and send it, and abort these steps | |
* 7. segment the buffer of blocks accepted from StatusDatasetContext::append | |
* into one or more segments under the allocated version, | |
* such that the Data packets will not become too large after signing | |
* 8. set FinalBlockId on at least the last segment | |
* 9. sign the Data packets and add them to the internal InMemoryStorage, | |
* and send segment 0 with CachePolicy=NoCache | |
* | |
* As an optimization, a Data packet may be sent as soon as enough octets have been collected | |
* through StatusDatasetContenxt::append calls. | |
*/ | |
void | |
addStatusDataset(const PartialName& relPrefix, | |
const Authorization& authorization, | |
const StatusDatasetHandler& handler); | |
// note: This design pushes all Data packets in the response to the forwarder, | |
// assuming the ContentStore will admit them. | |
// This will change in #2182. | |
public: // NotificationStream | |
/** \brief register a NotificationStream | |
* \param relPrefix a prefix for this notification stream, eg. "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 processing an Interest under NotificationStream prefix: | |
* 1. query the internal InMemoryStorage; | |
* if a match is found, send the Data with CachePolicy=NoCache | |
* | |
* 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 with CachePolicy=NoCache, | |
* and add it to the internal InMemoryStorage; | |
* note: it's sent to match any pending Interests; it's stored to match future Interests | |
*/ | |
PostNotification | |
addNotificationStream(const PartialName& relPrefix); | |
// note: This NotificationStream only works when there's exactly one top-level prefix. | |
// This fits today's use cases, but will be improved after #2182. | |
}; | |
} // namespace mgmt | |
} // namespace ndn | |
namespace ndn { | |
namespace nfd { | |
class ControlParameters : public ::ndn::mgmt::ControlParameters | |
{ | |
// (omitted) | |
}; | |
} // namespace nfd | |
} // namespace ndn | |
namespace nfd { | |
/** \brief a router face that is connected to InternalClientFace | |
*/ | |
class InternalFace : public nfd::Face | |
{ | |
}; | |
/** \brief a client face that is connected to NFD InternalFace | |
*/ | |
class InternalClientFace : public ndn::Face | |
{ | |
public: | |
/** \brief constructor a client face and hook to an InternalFace | |
*/ | |
explicit | |
InternalClientFace(InternalFace& internalFace); | |
}; | |
} // namespace nfd | |
// example for FaceManager | |
namespace nfd { | |
class ManagementAuthorization : noncopyable | |
{ | |
public: | |
explicit | |
ManagementAuthorization(ConfigFile section); | |
void | |
authorize(const Name& prefix, const Interest& interest, | |
const ::ndn::mgmt::ControlParameters* params, | |
::ndn::mgmt::AcceptContinuation accept, ::ndn::mgmt::RejectContinuation reject) const | |
{ | |
if (/* signature is bad, or certificate is unknown */) { | |
reject(RejectReply::STATUS403); | |
return; | |
} | |
name::Component module = interest.getName().at(prefix.size()); | |
if (/* certificate has the privilege */) { | |
accept(/* username of the certificate owner */); | |
} | |
else { | |
reject(RejectReply::STATUS403); | |
} | |
} | |
}; | |
class FaceManager | |
{ | |
public: | |
void | |
dispatcherRegister(::nfd::mgmt::Dispatcher& dispatcher) | |
{ | |
auto commandAuth = bind(&ManagementAuthorization::authorize, m_auth, _1, _2, _3, _4, _5); | |
auto acceptAllAuth = ::ndn::mgmt::makeAcceptAllAuthorization(); | |
auto validateCreateParams = ::ndn::nfd::makeRequestParameterValidator<::ndn::nfd::FaceCreateCommand>(); | |
dispatcher.addControlCommand("faces/create", commandAuth, validateCreateParams, | |
bind(&FaceManager::handleCreateCommand, this, _1, _2, _3, _4)); | |
dispatcher.addStatusDataset("faces/list", acceptAllAuth, | |
bind(&FaceManager::handleListRequest, this, _1, _2, _3)); | |
dispatcher.addStatusDataset("faces/query", acceptAllAuth, | |
bind(&FaceManager::handleQueryRequest, this, _1, _2, _3)); | |
this->m_postStatusChangeNotification = dispatcher.addNotificationStream("faces/events"); | |
this->startNotificationStream(); | |
} | |
void | |
startNotificationStream() | |
{ | |
m_faceTable.onAdd += [] (shared_ptr<Face> face) { | |
FaceEvent evt = {CREATED, face}; | |
m_postStatusChangeNotification(evt.wireEncode()); | |
} | |
} | |
private: | |
void | |
handleCreateCommand(const Name& prefix, const Interest& interest, | |
const ::ndn::mgmt::ControlParameters& params, | |
const CommandContinuation& done) | |
{ | |
const ::ndn::nfd::ControlParameters& nfdParams = static_cast<const ::ndn::nfd::ControlParameters&>(params); | |
createFace(nfdParams.faceUri, | |
[done] (FaceId faceId) { | |
ControlResponse resp; | |
/* populate resp */ | |
done(resp); | |
}, | |
[done] { | |
ControlResponse resp; | |
/* populate resp */ | |
done(resp); | |
}); | |
} | |
void | |
handleListRequest(const Name& prefix, const Interest& interest, | |
::ndn::mgmt::StatusDatasetContext& context) | |
{ | |
for (const Face& face : m_faceTable) { | |
context.append(face.getFaceStatus().wireEncode()); | |
} | |
context.end(); | |
} | |
void | |
handleQueryRequest(const Name& prefix, const Interest& interest, | |
::ndn::mgmt::StatusDatasetContext& context) | |
{ | |
const name::Component& filterComponent = interest.getName().at(prefix.size() + PartialName("faces/query").size()); | |
FaceQueryFilter filter; | |
try { | |
filter.wireDecode(filterComponent); | |
} | |
catch (tlv::Error&) { | |
context.reject(); | |
return; | |
} | |
for (const Face& face : m_faceTable) { | |
if (filter.matches(face)) { | |
context.append(face.getFaceStatus().wireEncode()); | |
} | |
} | |
context.end(); | |
} | |
private: | |
FaceTable m_faceTable; | |
ManagementAuthorization m_auth; | |
KeyChain m_keyChain; | |
::ndn::mgmt::PostNotification m_postStatusChangeNotification; | |
}; | |
} // namespace nfd |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment