Skip to content

Instantly share code, notes, and snippets.

@yoursunny
Last active March 9, 2016 22:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yoursunny/e3c520a590af86a0bb37 to your computer and use it in GitHub Desktop.
Save yoursunny/e3c520a590af86a0bb37 to your computer and use it in GitHub Desktop.
NFD management dispatcher http://redmine.named-data.net/issues/2200
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 reply with an application NACK
*/
NACK
};
/** \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,
AcceptContinuation accept, 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
*/
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,
CommandContinuation done)> ControlCommandHandler;
// ---- StatusDataset ----
/** \brief a function to be called for appending to reply dataset
* \param block a Block to be appended
*/
typedef std::function<void(const Block& block)> StatusDatasetAppend;
/** \brief a function to be called when all blocks have been appended
*/
typedef std::function<void()> StatusDatasetEnd;
/** \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
*
* 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,
StatusDatasetAppend append,
StatusDatasetEnd end)> 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
*/
Dispatcher(Face& face, security::KeyChain& keyChain, const security::SigningInfo& signingInfo);
virtual
~Dispatcher();
/** \brief register a top-level prefix in Face
* \param prefix a top-level prefix, eg. "/localhost/nfd"
* \param signingInfo signing parameters to sign the prefix registration command
* \return a RegisteredPrefixId which can be used to cancel the registration
*/
const RegisteredPrefixId*
registerTopPrefix(const Name& prefix, 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
protected:
/** \brief process an Interest
* \param prefix the top-level prefix
*/
void
processInterest(const Name& prefix, const Interest& interest);
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), otherwise it's undefined behavior
*
* 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, eg. "faces/list";
* relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be non-overlapping
* (no relPrefix is a prefix of another relPrefix), otherwise it's undefined behavior
* \param authorization should set identity to Name() if the dataset is public
*
* 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, eg. 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, eg. "faces/events";
* relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be non-overlapping
* (no relPrefix is a prefix of another relPrefix), otherwise it's undefined behavior
* \return a function into which notifications can be posted
*
* Procedure for posting a notification:
* 1. assign the next sequence number to the notification
* 2. place the notification block into one Data packet
* 3. sign the Data packet
* 4. if the Data packet is too large, abort these steps and log an error
* 5. send the signed Data packet
*/
PostNotification
addNotificationStream(const PartialName& relPrefix);
};
} // namespace mgmt
} // namespace ndn
namespace ndn {
namespace nfd {
class ControlParameters : public ::ndn::mgmt::ControlParameters
{
// (omitted)
};
/** \tparam CC a subclass of ::ndn::nfd::ControlCommand
* \return a ValidateParameters constructed from CC::validateRequest
*/
template<typename CC>
::ndn::mgmt::ValidateParameters
makeRequestParameterValidator();
} // 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, _4));
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,
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::StatusDatasetAppend append, ::ndn::mgmt::StatusDatasetEnd end)
{
for (const Face& face : m_faceTable) {
append(face.getFaceStatus().wireEncode())
}
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