Yanbiao Li | 8ee37ed | 2015-05-19 12:44:04 -0700 | [diff] [blame] | 1 | /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ |
| 2 | /** |
Alexander Afanasyev | 80b68e1 | 2015-09-17 17:01:04 -0700 | [diff] [blame^] | 3 | * Copyright (c) 2013-2015 Regents of the University of California. |
Yanbiao Li | 8ee37ed | 2015-05-19 12:44:04 -0700 | [diff] [blame] | 4 | * |
Alexander Afanasyev | 80b68e1 | 2015-09-17 17:01:04 -0700 | [diff] [blame^] | 5 | * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). |
Yanbiao Li | 8ee37ed | 2015-05-19 12:44:04 -0700 | [diff] [blame] | 6 | * |
Alexander Afanasyev | 80b68e1 | 2015-09-17 17:01:04 -0700 | [diff] [blame^] | 7 | * ndn-cxx library is free software: you can redistribute it and/or modify it under the |
| 8 | * terms of the GNU Lesser General Public License as published by the Free Software |
| 9 | * Foundation, either version 3 of the License, or (at your option) any later version. |
Yanbiao Li | 8ee37ed | 2015-05-19 12:44:04 -0700 | [diff] [blame] | 10 | * |
Alexander Afanasyev | 80b68e1 | 2015-09-17 17:01:04 -0700 | [diff] [blame^] | 11 | * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY |
| 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
| 13 | * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. |
Yanbiao Li | 8ee37ed | 2015-05-19 12:44:04 -0700 | [diff] [blame] | 14 | * |
Alexander Afanasyev | 80b68e1 | 2015-09-17 17:01:04 -0700 | [diff] [blame^] | 15 | * You should have received copies of the GNU General Public License and GNU Lesser |
| 16 | * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see |
| 17 | * <http://www.gnu.org/licenses/>. |
| 18 | * |
| 19 | * See AUTHORS.md for complete list of ndn-cxx authors and contributors. |
Yanbiao Li | 8ee37ed | 2015-05-19 12:44:04 -0700 | [diff] [blame] | 20 | */ |
| 21 | |
| 22 | #ifndef NDN_MGMT_DISPATCHER_HPP |
| 23 | #define NDN_MGMT_DISPATCHER_HPP |
| 24 | |
| 25 | #include "../face.hpp" |
| 26 | #include "../security/key-chain.hpp" |
| 27 | #include "../encoding/block.hpp" |
| 28 | #include "control-response.hpp" |
| 29 | #include "control-parameters.hpp" |
| 30 | #include "status-dataset-context.hpp" |
| 31 | |
| 32 | #include <unordered_map> |
| 33 | |
| 34 | namespace ndn { |
| 35 | namespace mgmt { |
| 36 | |
| 37 | // ---- AUTHORIZATION ---- |
| 38 | |
| 39 | /** \brief a function to be called if authorization is successful |
| 40 | * \param requester a string that indicates the requester, whose semantics is determined by |
| 41 | * the Authorization function; this value is intended for logging only, |
| 42 | * and should not affect how the request is processed |
| 43 | */ |
| 44 | typedef std::function<void(const std::string& requester)> AcceptContinuation; |
| 45 | |
| 46 | /** \brief indicate how to reply in case authorization is rejected |
| 47 | */ |
| 48 | enum class RejectReply { |
| 49 | /** \brief do not reply |
| 50 | */ |
| 51 | SILENT, |
| 52 | /** \brief reply with a ControlResponse where StatusCode is 403 |
| 53 | */ |
| 54 | STATUS403 |
| 55 | }; |
| 56 | |
| 57 | /** \brief a function to be called if authorization is rejected |
| 58 | */ |
| 59 | typedef std::function<void(RejectReply act)> RejectContinuation; |
| 60 | |
| 61 | /** \brief a function that performs authorization |
| 62 | * \param prefix top-level prefix, e.g., "/localhost/nfd"; |
| 63 | * This argument can be inspected to allow Interests only under a subset of |
| 64 | * top-level prefixes (e.g., allow "/localhost/nfd" only), |
| 65 | * or to use different trust model regarding to the prefix. |
| 66 | * \param interest incoming Interest |
| 67 | * \param params parsed ControlParameters for ControlCommand, otherwise nullptr; |
| 68 | * This is guaranteed to be not-null and have correct type for the command, |
| 69 | * but may not be valid (e.g., can have missing fields). |
| 70 | * |
| 71 | * Either accept or reject must be called after authorization completes. |
| 72 | */ |
| 73 | typedef std::function<void(const Name& prefix, const Interest& interest, |
| 74 | const ControlParameters* params, |
Junxiao Shi | f65a336 | 2015-09-06 20:54:54 -0700 | [diff] [blame] | 75 | const AcceptContinuation& accept, |
| 76 | const RejectContinuation& reject)> Authorization; |
Yanbiao Li | 8ee37ed | 2015-05-19 12:44:04 -0700 | [diff] [blame] | 77 | |
| 78 | /** \return an Authorization that accepts all Interests, with empty string as requester |
| 79 | */ |
| 80 | Authorization |
| 81 | makeAcceptAllAuthorization(); |
| 82 | |
| 83 | // ---- CONTROL COMMAND ---- |
| 84 | |
| 85 | /** \brief a function to validate input ControlParameters |
| 86 | * \param params parsed ControlParameters; |
| 87 | * This is guaranteed to have correct type for the command. |
| 88 | */ |
| 89 | typedef std::function<bool(const ControlParameters& params)> ValidateParameters; |
| 90 | |
| 91 | /** \brief a function to be called after ControlCommandHandler completes |
| 92 | * \param resp the response to be sent to requester |
| 93 | */ |
| 94 | typedef std::function<void(const ControlResponse& resp)> CommandContinuation; |
| 95 | |
| 96 | /** \brief a function to handle an authorized ControlCommand |
| 97 | * \param prefix top-level prefix, e.g., "/localhost/nfd"; |
| 98 | * \param interest incoming Interest |
| 99 | * \param params parsed ControlParameters; |
| 100 | * This is guaranteed to have correct type for the command, |
| 101 | * and is valid (e.g., has all required fields). |
| 102 | */ |
| 103 | typedef std::function<void(const Name& prefix, const Interest& interest, |
| 104 | const ControlParameters& params, |
Junxiao Shi | f65a336 | 2015-09-06 20:54:54 -0700 | [diff] [blame] | 105 | const CommandContinuation& done)> ControlCommandHandler; |
Yanbiao Li | 8ee37ed | 2015-05-19 12:44:04 -0700 | [diff] [blame] | 106 | |
| 107 | |
| 108 | /** \brief a function to handle a StatusDataset request |
| 109 | * \param prefix top-level prefix, e.g., "/localhost/nfd"; |
| 110 | * \param interest incoming Interest; its Name doesn't contain version and segment components |
| 111 | * |
| 112 | * This function can generate zero or more blocks and pass them to \p append, |
| 113 | * and must call \p end upon completion. |
| 114 | */ |
| 115 | typedef std::function<void(const Name& prefix, const Interest& interest, |
| 116 | StatusDatasetContext& context)> StatusDatasetHandler; |
| 117 | |
| 118 | //---- NOTIFICATION STREAM ---- |
| 119 | |
| 120 | /** \brief a function to post a notification |
| 121 | */ |
| 122 | typedef std::function<void(const Block& notification)> PostNotification; |
| 123 | |
| 124 | // ---- DISPATCHER ---- |
| 125 | |
| 126 | /** \brief represents a dispatcher on server side of NFD Management protocol |
| 127 | */ |
| 128 | class Dispatcher : noncopyable |
| 129 | { |
| 130 | class Error : public std::runtime_error |
| 131 | { |
| 132 | public: |
| 133 | explicit |
| 134 | Error(const std::string& what) |
| 135 | : std::runtime_error(what) |
| 136 | { |
| 137 | } |
| 138 | }; |
| 139 | |
| 140 | public: |
| 141 | /** \brief constructor |
| 142 | * \param face the Face on which the dispatcher operates |
| 143 | * \param keyChain a KeyChain to sign Data |
| 144 | * \param signingInfo signing parameters to sign Data with \p keyChain |
| 145 | */ |
| 146 | Dispatcher(Face& face, security::KeyChain& keyChain, |
| 147 | const security::SigningInfo& signingInfo = security::SigningInfo()); |
| 148 | |
| 149 | virtual |
| 150 | ~Dispatcher(); |
| 151 | |
| 152 | /** \brief add a top-level prefix |
| 153 | * \param prefix a top-level prefix, e.g., "/localhost/nfd" |
| 154 | * \param wantRegister whether prefix registration should be performed through the Face |
| 155 | * \param signingInfo signing parameters to sign the prefix registration command |
| 156 | * \throw std::out_of_range \p prefix overlaps with an existing top-level prefix |
| 157 | * |
| 158 | * Procedure for adding a top-level prefix: |
| 159 | * 1. if the new top-level prefix overlaps with an existing top-level prefix |
| 160 | * (one top-level prefix is a prefix of another top-level prefix), throw std::domain_error |
| 161 | * 2. if wantRegister is true, invoke face.registerPrefix for the top-level prefix; |
| 162 | * the returned RegisteredPrefixId shall be recorded internally, indexed by the top-level |
| 163 | * prefix |
| 164 | * 3. foreach relPrefix from ControlCommands and StatusDatasets, |
| 165 | * join the top-level prefix with the relPrefix to obtain the full prefix, |
| 166 | * and invoke non-registering overload of face.setInterestFilter, |
| 167 | * with the InterestHandler set to an appropriate private method to handle incoming Interests |
| 168 | * for the ControlCommand or StatusDataset; |
| 169 | * the returned InterestFilterId shall be recorded internally, indexed by the top-level |
| 170 | * prefix |
| 171 | */ |
| 172 | void |
| 173 | addTopPrefix(const Name& prefix, bool wantRegister = true, |
| 174 | const security::SigningInfo& signingInfo = security::SigningInfo()); |
| 175 | |
| 176 | /** \brief remove a top-level prefix |
| 177 | * \param prefix a top-level prefix, e.g., "/localhost/nfd" |
| 178 | * |
| 179 | * Procedure for removing a top-level prefix: |
| 180 | * 1. if the top-level prefix has not been added, abort these steps |
| 181 | * 2. if the top-level prefix has been added with wantRegister, |
| 182 | * invoke face.unregisterPrefix with the RegisteredPrefixId |
| 183 | * 3. foreach InterestFilterId recorded during addTopPrefix, |
| 184 | * invoke face.unsetInterestFilter with the InterestFilterId |
| 185 | */ |
| 186 | void |
| 187 | removeTopPrefix(const Name& prefix); |
| 188 | |
| 189 | public: // ControlCommand |
| 190 | /** \brief register a ControlCommand |
| 191 | * \tparam CP subclass of ControlParameters used by this command |
| 192 | * \param relPrefix a prefix for this command, e.g., "faces/create"; |
| 193 | * relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be |
| 194 | * non-overlapping |
| 195 | * (no relPrefix is a prefix of another relPrefix) |
| 196 | * \pre no top-level prefix has been added |
| 197 | * \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix |
| 198 | * \throw std::domain_error one or more top-level prefix has been added |
| 199 | * |
| 200 | * Procedure for processing a ControlCommand: |
| 201 | * 1. extract the NameComponent containing ControlParameters (the component after relPrefix), |
| 202 | * and parse ControlParameters into type CP; if parsing fails, abort these steps |
| 203 | * 2. perform authorization; if authorization is rejected, |
| 204 | * perform the RejectReply action, and abort these steps |
| 205 | * 3. validate ControlParameters; if validation fails, |
| 206 | * make ControlResponse with StatusCode 400, and go to step 5 |
| 207 | * 4. invoke handler, wait until CommandContinuation is called |
| 208 | * 5. encode the ControlResponse into one Data packet |
| 209 | * 6. sign the Data packet |
| 210 | * 7. if the Data packet is too large, abort these steps and log an error |
| 211 | * 8. send the signed Data packet |
| 212 | */ |
| 213 | template<typename CP> |
| 214 | void |
| 215 | addControlCommand(const PartialName& relPrefix, |
Junxiao Shi | f65a336 | 2015-09-06 20:54:54 -0700 | [diff] [blame] | 216 | const Authorization& authorization, |
| 217 | const ValidateParameters& validateParams, |
| 218 | const ControlCommandHandler& handler); |
Yanbiao Li | 8ee37ed | 2015-05-19 12:44:04 -0700 | [diff] [blame] | 219 | |
| 220 | public: // StatusDataset |
| 221 | /** \brief register a StatusDataset or a prefix under which StatusDatasets can be requested |
| 222 | * \param relPrefix a prefix for this dataset, e.g., "faces/list"; |
| 223 | * relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be |
| 224 | * non-overlapping |
| 225 | * (no relPrefix is a prefix of another relPrefix) |
| 226 | * \param authorization should set identity to Name() if the dataset is public |
| 227 | * \pre no top-level prefix has been added |
| 228 | * \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix |
| 229 | * \throw std::domain_error one or more top-level prefix has been added |
| 230 | * |
| 231 | * The payload of the returned status dataset data packet is at most half of the maximum |
| 232 | * data packet size. |
| 233 | * |
| 234 | * Procedure for processing a StatusDataset request: |
| 235 | * 1. if the request Interest contains version or segment components, abort these steps; |
| 236 | * note: the request may contain more components after relPrefix, e.g., a query condition |
| 237 | * 2. perform authorization; if authorization is rejected, |
| 238 | * perform the RejectReply action, and abort these steps |
| 239 | * 3. invoke handler, store blocks passed to StatusDatasetAppend calls in a buffer, |
| 240 | * wait until StatusDatasetEnd is called |
| 241 | * 4. allocate a version |
| 242 | * 5. segment the buffer into one or more segments under the allocated version, |
| 243 | * such that the Data packets will not become too large after signing |
| 244 | * 6. set FinalBlockId on at least the last segment |
| 245 | * 7. sign the Data packets |
| 246 | * 8. send the signed Data packets |
| 247 | * |
| 248 | * As an optimization, a Data packet may be sent as soon as enough octets have been collected |
| 249 | * through StatusDatasetAppend calls. |
| 250 | */ |
| 251 | void |
| 252 | addStatusDataset(const PartialName& relPrefix, |
Junxiao Shi | f65a336 | 2015-09-06 20:54:54 -0700 | [diff] [blame] | 253 | const Authorization& authorization, |
| 254 | const StatusDatasetHandler& handler); |
Yanbiao Li | 8ee37ed | 2015-05-19 12:44:04 -0700 | [diff] [blame] | 255 | |
| 256 | public: // NotificationStream |
| 257 | /** \brief register a NotificationStream |
| 258 | * \param relPrefix a prefix for this notification stream, e.g., "faces/events"; |
| 259 | * relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be |
| 260 | * non-overlapping |
| 261 | * (no relPrefix is a prefix of another relPrefix) |
| 262 | * \return a function into which notifications can be posted |
| 263 | * \pre no top-level prefix has been added |
| 264 | * \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix |
| 265 | * \throw std::domain_error one or more top-level prefix has been added |
| 266 | * |
| 267 | * Procedure for posting a notification: |
| 268 | * 1. if no top-level prefix has been added, or more than one top-level prefixes have been |
| 269 | * added, |
| 270 | * abort these steps and log an error |
| 271 | * 2. assign the next sequence number to the notification |
| 272 | * 3. place the notification block into one Data packet under the sole top-level prefix |
| 273 | * 4. sign the Data packet |
| 274 | * 5. if the Data packet is too large, abort these steps and log an error |
| 275 | * 6. send the signed Data packet |
| 276 | */ |
| 277 | PostNotification |
| 278 | addNotificationStream(const PartialName& relPrefix); |
| 279 | |
| 280 | private: |
| 281 | typedef std::function<void(const Name& prefix, |
| 282 | const Interest& interest)> InterestHandler; |
| 283 | |
| 284 | typedef std::function<void(const std::string& requester, |
| 285 | const Name& prefix, |
| 286 | const Interest& interest, |
| 287 | const ControlParameters*)> AuthorizationAcceptedCallback; |
| 288 | |
| 289 | typedef std::function<void(RejectReply act, |
| 290 | const Interest& interest)> AuthorizationRejectedCallback; |
| 291 | |
| 292 | /** |
| 293 | * @brief the parser of extracting control parameters from name component. |
| 294 | * |
| 295 | * @param component name component that may encode control parameters. |
| 296 | * @return a shared pointer to the extracted control parameters. |
| 297 | * @throw tlv::Error if the NameComponent cannot be parsed as ControlParameters |
| 298 | */ |
| 299 | typedef std::function<shared_ptr<ControlParameters>(const name::Component& component)> |
| 300 | ControlParametersParser; |
| 301 | |
| 302 | bool |
| 303 | isOverlappedWithOthers(const PartialName& relPrefix); |
| 304 | |
| 305 | /** |
| 306 | * @brief process unauthorized request |
| 307 | * |
| 308 | * @param act action to reply |
| 309 | * @param interest the incoming Interest |
| 310 | */ |
| 311 | void |
| 312 | afterAuthorizationRejected(RejectReply act, const Interest& interest); |
| 313 | |
| 314 | void |
| 315 | sendData(const Name& dataName, const Block& content, |
| 316 | const MetaInfo& metaInfo); |
| 317 | |
| 318 | /** |
| 319 | * @brief process the control-command Interest before authorization. |
| 320 | * |
| 321 | * @param prefix the top-level prefix |
| 322 | * @param relPrefix the relative prefix |
| 323 | * @param interest the incoming Interest |
| 324 | * @param parser to extract control parameters from the \p interest |
| 325 | * @param authorization to process validation on this command |
| 326 | * @param accepted the callback for successful authorization |
| 327 | * @param rejected the callback for failed authorization |
| 328 | */ |
| 329 | void |
| 330 | processControlCommandInterest(const Name& prefix, |
| 331 | const Name& relPrefix, |
| 332 | const Interest& interest, |
| 333 | const ControlParametersParser& parser, |
| 334 | const Authorization& authorization, |
| 335 | const AuthorizationAcceptedCallback& accepted, |
| 336 | const AuthorizationRejectedCallback& rejected); |
| 337 | |
| 338 | /** |
| 339 | * @brief process the authorized control-command. |
| 340 | * |
| 341 | * @param requester the requester |
| 342 | * @param prefix the top-level prefix |
| 343 | * @param interest the incoming Interest |
| 344 | * @param parameters control parameters of this command |
| 345 | * @param validate to validate control parameters |
| 346 | * @param handler to process this command |
| 347 | */ |
| 348 | void |
| 349 | processAuthorizedControlCommandInterest(const std::string& requester, |
| 350 | const Name& prefix, |
| 351 | const Interest& interest, |
| 352 | const ControlParameters* parameters, |
| 353 | const ValidateParameters& validate, |
| 354 | const ControlCommandHandler& handler); |
| 355 | |
| 356 | void |
| 357 | sendControlResponse(const ControlResponse& resp, const Interest& interest, bool isNack = false); |
| 358 | |
| 359 | /** |
| 360 | * @brief process the status-dataset Interest before authorization. |
| 361 | * |
| 362 | * @param prefix the top-level prefix |
| 363 | * @param interest the incoming Interest |
| 364 | * @param authorization to process verification |
| 365 | * @param accepted callback for successful authorization |
| 366 | * @param rejected callback for failed authorization |
| 367 | */ |
| 368 | void |
| 369 | processStatusDatasetInterest(const Name& prefix, |
| 370 | const Interest& interest, |
| 371 | const Authorization& authorization, |
| 372 | const AuthorizationAcceptedCallback& accepted, |
| 373 | const AuthorizationRejectedCallback& rejected); |
| 374 | |
| 375 | /** |
| 376 | * @brief process the authorized status-dataset request |
| 377 | * |
| 378 | * @param requester the requester |
| 379 | * @param prefix the top-level prefix |
| 380 | * @param interest the incoming Interest |
| 381 | * @param handler to process this request |
| 382 | */ |
| 383 | void |
| 384 | processAuthorizedStatusDatasetInterest(const std::string& requester, |
| 385 | const Name& prefix, |
| 386 | const Interest& interest, |
| 387 | const StatusDatasetHandler& handler); |
| 388 | |
| 389 | void |
| 390 | postNotification(const Block& notification, const PartialName& relPrefix); |
| 391 | |
| 392 | private: |
| 393 | struct TopPrefixEntry |
| 394 | { |
| 395 | Name topPrefix; |
| 396 | bool wantRegister; |
| 397 | const ndn::RegisteredPrefixId* registerPrefixId; |
| 398 | std::vector<const ndn::InterestFilterId*> interestFilters; |
| 399 | }; |
| 400 | std::unordered_map<Name, TopPrefixEntry> m_topLevelPrefixes; |
| 401 | |
| 402 | Face& m_face; |
| 403 | security::KeyChain& m_keyChain; |
| 404 | security::SigningInfo m_signingInfo; |
| 405 | |
| 406 | typedef std::unordered_map<PartialName, InterestHandler> HandlerMap; |
| 407 | typedef HandlerMap::iterator HandlerMapIt; |
| 408 | HandlerMap m_handlers; |
| 409 | |
| 410 | // NotificationStream name => next sequence number |
| 411 | std::unordered_map<Name, uint64_t> m_streams; |
| 412 | }; |
| 413 | |
| 414 | template<typename CP> |
| 415 | void |
| 416 | Dispatcher::addControlCommand(const PartialName& relPrefix, |
Junxiao Shi | f65a336 | 2015-09-06 20:54:54 -0700 | [diff] [blame] | 417 | const Authorization& authorization, |
| 418 | const ValidateParameters& validateParams, |
| 419 | const ControlCommandHandler& handler) |
Yanbiao Li | 8ee37ed | 2015-05-19 12:44:04 -0700 | [diff] [blame] | 420 | { |
| 421 | if (!m_topLevelPrefixes.empty()) { |
| 422 | throw std::domain_error("one or more top-level prefix has been added"); |
| 423 | } |
| 424 | |
| 425 | if (isOverlappedWithOthers(relPrefix)) { |
| 426 | throw std::out_of_range("relPrefix overlaps with another relPrefix"); |
| 427 | } |
| 428 | |
| 429 | ControlParametersParser parser = |
| 430 | [] (const name::Component& component) -> shared_ptr<ControlParameters> { |
| 431 | return make_shared<CP>(component.blockFromValue()); |
| 432 | }; |
| 433 | |
| 434 | AuthorizationAcceptedCallback accepted = |
| 435 | bind(&Dispatcher::processAuthorizedControlCommandInterest, this, |
| 436 | _1, _2, _3, _4, validateParams, handler); |
| 437 | |
| 438 | AuthorizationRejectedCallback rejected = |
| 439 | bind(&Dispatcher::afterAuthorizationRejected, this, _1, _2); |
| 440 | |
| 441 | m_handlers[relPrefix] = bind(&Dispatcher::processControlCommandInterest, this, |
| 442 | _1, relPrefix, _2, parser, authorization, accepted, rejected); |
| 443 | } |
| 444 | |
| 445 | } // namespace mgmt |
| 446 | } // namespace ndn |
| 447 | #endif // NDN_MGMT_DISPATCHER_HPP |