mgmt: remove one level of indirection in control command processing
Also refactor the check for overlapping prefixes in Dispatcher, make
error messages more informative, and cleanup doxygen documentation.
Change-Id: I5781524fdcde62b47d74c80b98db7d011250a226
diff --git a/ndn-cxx/mgmt/dispatcher.cpp b/ndn-cxx/mgmt/dispatcher.cpp
index b42fa36..f242eb1 100644
--- a/ndn-cxx/mgmt/dispatcher.cpp
+++ b/ndn-cxx/mgmt/dispatcher.cpp
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
- * Copyright (c) 2013-2023 Regents of the University of California.
+ * Copyright (c) 2013-2025 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
@@ -51,18 +51,15 @@
{
}
-Dispatcher::~Dispatcher() = default;
-
void
Dispatcher::addTopPrefix(const Name& prefix, bool wantRegister,
const security::SigningInfo& signingInfo)
{
- bool hasOverlap = std::any_of(m_topLevelPrefixes.begin(), m_topLevelPrefixes.end(),
- [&prefix] (const auto& x) {
- return x.first.isPrefixOf(prefix) || prefix.isPrefixOf(x.first);
- });
+ bool hasOverlap = std::any_of(m_topLevelPrefixes.begin(), m_topLevelPrefixes.end(), [&] (const auto& x) {
+ return x.first.isPrefixOf(prefix) || prefix.isPrefixOf(x.first);
+ });
if (hasOverlap) {
- NDN_THROW(std::out_of_range("top-level prefix overlaps"));
+ NDN_THROW(std::out_of_range("top-level prefix '" + prefix.toUri() + "' overlaps with another"));
}
TopPrefixEntry& topPrefixEntry = m_topLevelPrefixes[prefix];
@@ -92,21 +89,19 @@
m_topLevelPrefixes.erase(prefix);
}
-bool
-Dispatcher::isOverlappedWithOthers(const PartialName& relPrefix) const
+void
+Dispatcher::checkPrefix(const PartialName& relPrefix) const
{
- bool hasOverlapWithHandlers =
- std::any_of(m_handlers.begin(), m_handlers.end(),
- [&] (const auto& entry) {
- return entry.first.isPrefixOf(relPrefix) || relPrefix.isPrefixOf(entry.first);
- });
- bool hasOverlapWithStreams =
- std::any_of(m_streams.begin(), m_streams.end(),
- [&] (const auto& entry) {
- return entry.first.isPrefixOf(relPrefix) || relPrefix.isPrefixOf(entry.first);
- });
+ if (!m_topLevelPrefixes.empty()) {
+ NDN_THROW(std::domain_error("one or more top-level prefix has been added"));
+ }
- return hasOverlapWithHandlers || hasOverlapWithStreams;
+ bool hasOverlap = std::any_of(m_handlers.begin(), m_handlers.end(), [&] (const auto& entry) {
+ return entry.first.isPrefixOf(relPrefix) || relPrefix.isPrefixOf(entry.first);
+ });
+ if (hasOverlap) {
+ NDN_THROW(std::out_of_range("'" + relPrefix.toUri() + "' overlaps with another handler"));
+ }
}
void
@@ -166,13 +161,13 @@
}
void
-Dispatcher::processControlCommandInterest(const Name& prefix,
- const Name& relPrefix,
- const Interest& interest,
- const ControlParametersParser& parser,
- const Authorization& authorization,
- const AuthorizationAcceptedCallback& accepted,
- const AuthorizationRejectedCallback& rejected)
+Dispatcher::processCommand(const Name& prefix,
+ const Name& relPrefix,
+ const Interest& interest,
+ const ControlParametersParser& parse,
+ const Authorization& authorize,
+ const ValidateParameters& validateParams,
+ const ControlCommandHandler& handler)
{
// /<prefix>/<relPrefix>/<parameters>
size_t parametersLoc = prefix.size() + relPrefix.size();
@@ -180,24 +175,28 @@
shared_ptr<ControlParameters> parameters;
try {
- parameters = parser(pc);
+ parameters = parse(pc);
}
catch (const tlv::Error&) {
return;
}
- AcceptContinuation accept = [=] (const auto& req) { accepted(req, prefix, interest, parameters); };
- RejectContinuation reject = [=] (RejectReply reply) { rejected(reply, interest); };
- authorization(prefix, interest, parameters.get(), accept, reject);
+ AcceptContinuation accept = [=] (const auto& req) {
+ processAuthorizedCommand(req, prefix, interest, parameters, validateParams, handler);
+ };
+ RejectContinuation reject = [=] (RejectReply reply) {
+ afterAuthorizationRejected(reply, interest);
+ };
+ authorize(prefix, interest, parameters.get(), accept, reject);
}
void
-Dispatcher::processAuthorizedControlCommandInterest(const std::string& requester,
- const Name& prefix,
- const Interest& interest,
- const shared_ptr<ControlParameters>& parameters,
- const ValidateParameters& validateParams,
- const ControlCommandHandler& handler)
+Dispatcher::processAuthorizedCommand(const std::string& requester,
+ const Name& prefix,
+ const Interest& interest,
+ const shared_ptr<ControlParameters>& parameters,
+ const ValidateParameters& validateParams,
+ const ControlCommandHandler& handler)
{
if (validateParams(*parameters)) {
handler(prefix, interest, *parameters,
@@ -225,13 +224,7 @@
Authorization auth,
StatusDatasetHandler handler)
{
- if (!m_topLevelPrefixes.empty()) {
- NDN_THROW(std::domain_error("one or more top-level prefix has been added"));
- }
-
- if (isOverlappedWithOthers(relPrefix)) {
- NDN_THROW(std::out_of_range("status dataset name overlaps"));
- }
+ checkPrefix(relPrefix);
AuthorizationAcceptedCallback accept =
[this, handler = std::move(handler)] (auto&&, const auto& prefix, const auto& interest, auto&&) {
@@ -307,13 +300,7 @@
PostNotification
Dispatcher::addNotificationStream(const PartialName& relPrefix)
{
- if (!m_topLevelPrefixes.empty()) {
- NDN_THROW(std::domain_error("one or more top-level prefix has been added"));
- }
-
- if (isOverlappedWithOthers(relPrefix)) {
- NDN_THROW(std::out_of_range("notification stream name overlaps"));
- }
+ checkPrefix(relPrefix);
// register a handler for the subscriber of this notification stream
// keep silent if Interest does not match a stored notification
diff --git a/ndn-cxx/mgmt/dispatcher.hpp b/ndn-cxx/mgmt/dispatcher.hpp
index 9c5bdde..2c4585a 100644
--- a/ndn-cxx/mgmt/dispatcher.hpp
+++ b/ndn-cxx/mgmt/dispatcher.hpp
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
- * Copyright (c) 2013-2023 Regents of the University of California.
+ * Copyright (c) 2013-2025 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
@@ -146,9 +146,6 @@
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
@@ -184,68 +181,88 @@
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 authorize Callback to authorize the incoming commands
- * \param validate Callback to validate parameters of the incoming commands
- * \param handle 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
+ /**
+ * \brief Register a ControlCommand.
+ * \tparam ParametersType Concrete subclass of ControlParameters used by this command.
+ * \param relPrefix The name prefix for this command relative to the top-level prefix,
+ * e.g., "faces/create". The prefixes across all ControlCommands,
+ * StatusDatasets, and NotificationStreams must not overlap (no relPrefix
+ * is a prefix of another relPrefix).
+ * \param authorize Callback to authorize the incoming commands
+ * \param validate Callback to validate parameters of the incoming commands
+ * \param handle 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 prefixes have 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
+ * Procedure for processing a ControlCommand:
+ * 1. Extract the NameComponent containing ControlParameters (the component after relPrefix),
+ * and parse ControlParameters into ParametersType; if parsing fails, abort these steps.
+ * 2. Perform authorization; if the authorization is rejected, perform the RejectReply action
+ * and abort these steps.
+ * 3. Validate ControlParameters; if validation fails, create a ControlResponse with
+ * StatusCode 400 and go to step 5.
+ * 4. Invoke the command 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>
+ template<typename ParametersType,
+ std::enable_if_t<std::is_convertible_v<ParametersType*, ControlParameters*>, int> = 0>
void
addControlCommand(const PartialName& relPrefix,
Authorization authorize,
ValidateParameters validate,
- ControlCommandHandler handle);
+ ControlCommandHandler handle)
+ {
+ checkPrefix(relPrefix);
+
+ ControlParametersParser parse = [] (const name::Component& comp) -> shared_ptr<ControlParameters> {
+ return make_shared<ParametersType>(comp.blockFromValue());
+ };
+
+ m_handlers[relPrefix] = [this, relPrefix,
+ parse = std::move(parse),
+ authorize = std::move(authorize),
+ validate = std::move(validate),
+ handle = std::move(handle)] (const auto& prefix, const auto& interest) {
+ processCommand(prefix, relPrefix, interest, parse, authorize, validate, handle);
+ };
+ }
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 authorize should set identity to Name() if the dataset is public
- * \param handle 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
+ /**
+ * \brief Register a StatusDataset or a prefix under which StatusDatasets can be requested.
+ * \param relPrefix The name prefix for this dataset relative to the top-level prefix,
+ * e.g., "faces/list". The prefixes across all ControlCommands,
+ * StatusDatasets, and NotificationStreams must not overlap (no relPrefix
+ * is a prefix of another relPrefix).
+ * \param authorize should set identity to Name() if the dataset is public
+ * \param handle 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 prefixes have 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
+ * 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 the authorization is rejected, perform the RejectReply action
+ * and abort these steps.
+ * 3. Invoke the 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.
+ * 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,
@@ -253,24 +270,25 @@
StatusDatasetHandler handle);
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
+ /**
+ * \brief Register a NotificationStream.
+ * \param relPrefix The name prefix for this notification stream relative to the top-level prefix,
+ * e.g., "faces/events". The prefixes across all ControlCommands,
+ * StatusDatasets, and NotificationStreams must not overlap (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 prefixes have 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
+ * 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);
@@ -292,8 +310,8 @@
*/
using ControlParametersParser = std::function<shared_ptr<ControlParameters>(const name::Component&)>;
- bool
- isOverlappedWithOthers(const PartialName& relPrefix) const;
+ void
+ checkPrefix(const PartialName& relPrefix) const;
/**
* @brief Process unauthorized request.
@@ -304,9 +322,9 @@
afterAuthorizationRejected(RejectReply act, const Interest& interest);
/**
- * @brief Query Data the in-memory storage by a given Interest.
+ * @brief Query Data in the in-memory storage for a given Interest.
*
- * if the query fails, invoke @p missContinuation to process @p interest.
+ * If the query fails, invoke @p missContinuation to process @p interest.
*
* @param prefix the top-level prefix
* @param interest the request
@@ -319,7 +337,7 @@
NONE = 0,
FACE = 1,
IMS = 2,
- FACE_AND_IMS = 3
+ FACE_AND_IMS = 3,
};
/**
@@ -342,72 +360,70 @@
SendDestination destination);
/**
- * @brief Send out a data packt through the face.
- *
- * @param data the data packet to insert
+ * @brief Send out a Data packet through the face.
*/
void
sendOnFace(const Data& data);
/**
- * @brief Process the control-command Interest before authorization.
+ * @brief Process an incoming 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
+ * @param parse function to extract the control parameters from the command
+ * @param authorize function to determine whether the command is authorized
+ * @param validate function to validate the command parameters
+ * @param handler function to execute the command after authorization and validation
*/
void
- processControlCommandInterest(const Name& prefix,
- const Name& relPrefix,
- const Interest& interest,
- const ControlParametersParser& parser,
- const Authorization& authorization,
- const AuthorizationAcceptedCallback& accepted,
- const AuthorizationRejectedCallback& rejected);
+ processCommand(const Name& prefix,
+ const Name& relPrefix,
+ const Interest& interest,
+ const ControlParametersParser& parse,
+ const Authorization& authorize,
+ const ValidateParameters& validate,
+ const ControlCommandHandler& handler);
/**
- * @brief Process the authorized control-command.
+ * @brief Process an 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
+ * @param validate function to validate the command parameters
+ * @param handler function to execute the command after authorization and validation
*/
void
- processAuthorizedControlCommandInterest(const std::string& requester,
- const Name& prefix,
- const Interest& interest,
- const shared_ptr<ControlParameters>& parameters,
- const ValidateParameters& validate,
- const ControlCommandHandler& handler);
+ processAuthorizedCommand(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.
+ * @brief Process a StatusDataset Interest before authorization.
*
* @param prefix the top-level prefix
* @param interest the incoming Interest
- * @param authorization to process verification
+ * @param authorize function 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 Authorization& authorize,
const AuthorizationAcceptedCallback& accepted,
const AuthorizationRejectedCallback& rejected);
/**
- * @brief Process the authorized StatusDataset request.
+ * @brief Process an authorized StatusDataset request.
*
* @param prefix the top-level prefix
* @param interest the incoming Interest
@@ -452,41 +468,6 @@
InMemoryStorageFifo m_storage;
};
-template<typename CP>
-void
-Dispatcher::addControlCommand(const PartialName& relPrefix,
- Authorization authorize,
- ValidateParameters validate,
- ControlCommandHandler handle)
-{
- if (!m_topLevelPrefixes.empty()) {
- NDN_THROW(std::domain_error("one or more top-level prefix has been added"));
- }
-
- if (isOverlappedWithOthers(relPrefix)) {
- NDN_THROW(std::out_of_range("relPrefix overlaps with another relPrefix"));
- }
-
- ControlParametersParser parser = [] (const name::Component& comp) -> shared_ptr<ControlParameters> {
- return make_shared<CP>(comp.blockFromValue());
- };
- AuthorizationAcceptedCallback accepted = [this, validate = std::move(validate),
- handle = std::move(handle)] (auto&&... args) {
- processAuthorizedControlCommandInterest(std::forward<decltype(args)>(args)..., validate, handle);
- };
- AuthorizationRejectedCallback rejected = [this] (auto&&... args) {
- afterAuthorizationRejected(std::forward<decltype(args)>(args)...);
- };
-
- m_handlers[relPrefix] = [this, relPrefix,
- parser = std::move(parser),
- authorize = std::move(authorize),
- accepted = std::move(accepted),
- rejected = std::move(rejected)] (const auto& prefix, const auto& interest) {
- processControlCommandInterest(prefix, relPrefix, interest, parser, authorize, accepted, rejected);
- };
-}
-
} // namespace ndn::mgmt
#endif // NDN_CXX_MGMT_DISPATCHER_HPP