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