blob: 7df61f79af3032eb1389d79252d04e7b42e730ec [file] [log] [blame]
Yanbiao Li8ee37ed2015-05-19 12:44:04 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
Alexander Afanasyev4c9a3d52017-01-03 17:45:19 -08003 * Copyright (c) 2013-2017 Regents of the University of California.
Yanbiao Li8ee37ed2015-05-19 12:44:04 -07004 *
Alexander Afanasyev80b68e12015-09-17 17:01:04 -07005 * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
Yanbiao Li8ee37ed2015-05-19 12:44:04 -07006 *
Alexander Afanasyev80b68e12015-09-17 17:01:04 -07007 * 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 Li8ee37ed2015-05-19 12:44:04 -070010 *
Alexander Afanasyev80b68e12015-09-17 17:01:04 -070011 * 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 Li8ee37ed2015-05-19 12:44:04 -070014 *
Alexander Afanasyev80b68e12015-09-17 17:01:04 -070015 * 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 Li8ee37ed2015-05-19 12:44:04 -070020 */
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"
Yanbiao Li4b4f7542016-03-11 02:04:43 +080028#include "../util/in-memory-storage-fifo.hpp"
Yanbiao Li8ee37ed2015-05-19 12:44:04 -070029#include "control-response.hpp"
30#include "control-parameters.hpp"
31#include "status-dataset-context.hpp"
32
33#include <unordered_map>
34
35namespace ndn {
36namespace mgmt {
37
38// ---- AUTHORIZATION ----
39
40/** \brief a function to be called if authorization is successful
41 * \param requester a string that indicates the requester, whose semantics is determined by
42 * the Authorization function; this value is intended for logging only,
43 * and should not affect how the request is processed
44 */
45typedef std::function<void(const std::string& requester)> AcceptContinuation;
46
47/** \brief indicate how to reply in case authorization is rejected
48 */
49enum class RejectReply {
50 /** \brief do not reply
51 */
52 SILENT,
53 /** \brief reply with a ControlResponse where StatusCode is 403
54 */
55 STATUS403
56};
57
58/** \brief a function to be called if authorization is rejected
59 */
60typedef std::function<void(RejectReply act)> RejectContinuation;
61
62/** \brief a function that performs authorization
63 * \param prefix top-level prefix, e.g., "/localhost/nfd";
64 * This argument can be inspected to allow Interests only under a subset of
65 * top-level prefixes (e.g., allow "/localhost/nfd" only),
66 * or to use different trust model regarding to the prefix.
67 * \param interest incoming Interest
68 * \param params parsed ControlParameters for ControlCommand, otherwise nullptr;
69 * This is guaranteed to be not-null and have correct type for the command,
70 * but may not be valid (e.g., can have missing fields).
71 *
72 * Either accept or reject must be called after authorization completes.
73 */
74typedef std::function<void(const Name& prefix, const Interest& interest,
75 const ControlParameters* params,
Junxiao Shif65a3362015-09-06 20:54:54 -070076 const AcceptContinuation& accept,
77 const RejectContinuation& reject)> Authorization;
Yanbiao Li8ee37ed2015-05-19 12:44:04 -070078
79/** \return an Authorization that accepts all Interests, with empty string as requester
80 */
81Authorization
82makeAcceptAllAuthorization();
83
84// ---- CONTROL COMMAND ----
85
86/** \brief a function to validate input ControlParameters
87 * \param params parsed ControlParameters;
88 * This is guaranteed to have correct type for the command.
89 */
90typedef std::function<bool(const ControlParameters& params)> ValidateParameters;
91
92/** \brief a function to be called after ControlCommandHandler completes
93 * \param resp the response to be sent to requester
94 */
95typedef std::function<void(const ControlResponse& resp)> CommandContinuation;
96
97/** \brief a function to handle an authorized ControlCommand
98 * \param prefix top-level prefix, e.g., "/localhost/nfd";
99 * \param interest incoming Interest
100 * \param params parsed ControlParameters;
101 * This is guaranteed to have correct type for the command,
102 * and is valid (e.g., has all required fields).
103 */
104typedef std::function<void(const Name& prefix, const Interest& interest,
105 const ControlParameters& params,
Junxiao Shif65a3362015-09-06 20:54:54 -0700106 const CommandContinuation& done)> ControlCommandHandler;
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700107
108
109/** \brief a function to handle a StatusDataset request
110 * \param prefix top-level prefix, e.g., "/localhost/nfd";
111 * \param interest incoming Interest; its Name doesn't contain version and segment components
112 *
113 * This function can generate zero or more blocks and pass them to \p append,
114 * and must call \p end upon completion.
115 */
116typedef std::function<void(const Name& prefix, const Interest& interest,
117 StatusDatasetContext& context)> StatusDatasetHandler;
118
119//---- NOTIFICATION STREAM ----
120
121/** \brief a function to post a notification
122 */
123typedef std::function<void(const Block& notification)> PostNotification;
124
125// ---- DISPATCHER ----
126
127/** \brief represents a dispatcher on server side of NFD Management protocol
128 */
129class Dispatcher : noncopyable
130{
131 class Error : public std::runtime_error
132 {
133 public:
134 explicit
135 Error(const std::string& what)
136 : std::runtime_error(what)
137 {
138 }
139 };
140
141public:
142 /** \brief constructor
143 * \param face the Face on which the dispatcher operates
144 * \param keyChain a KeyChain to sign Data
145 * \param signingInfo signing parameters to sign Data with \p keyChain
Yanbiao Li4b4f7542016-03-11 02:04:43 +0800146 * \param imsCapacity capacity of the internal InMemoryStorage used by dispatcher
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700147 */
Alexander Afanasyev4c9a3d52017-01-03 17:45:19 -0800148 Dispatcher(Face& face, security::v1::KeyChain& keyChain,
Yanbiao Li4b4f7542016-03-11 02:04:43 +0800149 const security::SigningInfo& signingInfo = security::SigningInfo(),
150 size_t imsCapacity = 256);
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700151
152 virtual
153 ~Dispatcher();
154
155 /** \brief add a top-level prefix
156 * \param prefix a top-level prefix, e.g., "/localhost/nfd"
157 * \param wantRegister whether prefix registration should be performed through the Face
158 * \param signingInfo signing parameters to sign the prefix registration command
159 * \throw std::out_of_range \p prefix overlaps with an existing top-level prefix
160 *
161 * Procedure for adding a top-level prefix:
162 * 1. if the new top-level prefix overlaps with an existing top-level prefix
163 * (one top-level prefix is a prefix of another top-level prefix), throw std::domain_error
164 * 2. if wantRegister is true, invoke face.registerPrefix for the top-level prefix;
165 * the returned RegisteredPrefixId shall be recorded internally, indexed by the top-level
166 * prefix
167 * 3. foreach relPrefix from ControlCommands and StatusDatasets,
168 * join the top-level prefix with the relPrefix to obtain the full prefix,
169 * and invoke non-registering overload of face.setInterestFilter,
170 * with the InterestHandler set to an appropriate private method to handle incoming Interests
171 * for the ControlCommand or StatusDataset;
172 * the returned InterestFilterId shall be recorded internally, indexed by the top-level
173 * prefix
174 */
175 void
176 addTopPrefix(const Name& prefix, bool wantRegister = true,
177 const security::SigningInfo& signingInfo = security::SigningInfo());
178
179 /** \brief remove a top-level prefix
180 * \param prefix a top-level prefix, e.g., "/localhost/nfd"
181 *
182 * Procedure for removing a top-level prefix:
183 * 1. if the top-level prefix has not been added, abort these steps
184 * 2. if the top-level prefix has been added with wantRegister,
185 * invoke face.unregisterPrefix with the RegisteredPrefixId
186 * 3. foreach InterestFilterId recorded during addTopPrefix,
187 * invoke face.unsetInterestFilter with the InterestFilterId
188 */
189 void
190 removeTopPrefix(const Name& prefix);
191
192public: // ControlCommand
193 /** \brief register a ControlCommand
194 * \tparam CP subclass of ControlParameters used by this command
195 * \param relPrefix a prefix for this command, e.g., "faces/create";
196 * relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be
197 * non-overlapping
198 * (no relPrefix is a prefix of another relPrefix)
Alexander Afanasyevf2a46222015-09-17 18:01:30 -0700199 * \param authorization Callback to authorize the incoming commands
200 * \param validateParams Callback to validate parameters of the incoming commands
201 * \param handler Callback to handle the commands
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700202 * \pre no top-level prefix has been added
203 * \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix
204 * \throw std::domain_error one or more top-level prefix has been added
205 *
206 * Procedure for processing a ControlCommand:
207 * 1. extract the NameComponent containing ControlParameters (the component after relPrefix),
208 * and parse ControlParameters into type CP; if parsing fails, abort these steps
209 * 2. perform authorization; if authorization is rejected,
210 * perform the RejectReply action, and abort these steps
211 * 3. validate ControlParameters; if validation fails,
212 * make ControlResponse with StatusCode 400, and go to step 5
213 * 4. invoke handler, wait until CommandContinuation is called
214 * 5. encode the ControlResponse into one Data packet
215 * 6. sign the Data packet
216 * 7. if the Data packet is too large, abort these steps and log an error
217 * 8. send the signed Data packet
218 */
219 template<typename CP>
220 void
221 addControlCommand(const PartialName& relPrefix,
Junxiao Shif65a3362015-09-06 20:54:54 -0700222 const Authorization& authorization,
223 const ValidateParameters& validateParams,
224 const ControlCommandHandler& handler);
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700225
226public: // StatusDataset
227 /** \brief register a StatusDataset or a prefix under which StatusDatasets can be requested
228 * \param relPrefix a prefix for this dataset, e.g., "faces/list";
229 * relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be
230 * non-overlapping
231 * (no relPrefix is a prefix of another relPrefix)
232 * \param authorization should set identity to Name() if the dataset is public
Alexander Afanasyevf2a46222015-09-17 18:01:30 -0700233 * \param handler Callback to process the incoming dataset requests
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700234 * \pre no top-level prefix has been added
235 * \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix
236 * \throw std::domain_error one or more top-level prefix has been added
237 *
238 * The payload of the returned status dataset data packet is at most half of the maximum
239 * data packet size.
240 *
241 * Procedure for processing a StatusDataset request:
242 * 1. if the request Interest contains version or segment components, abort these steps;
243 * note: the request may contain more components after relPrefix, e.g., a query condition
244 * 2. perform authorization; if authorization is rejected,
245 * perform the RejectReply action, and abort these steps
246 * 3. invoke handler, store blocks passed to StatusDatasetAppend calls in a buffer,
247 * wait until StatusDatasetEnd is called
248 * 4. allocate a version
249 * 5. segment the buffer into one or more segments under the allocated version,
250 * such that the Data packets will not become too large after signing
251 * 6. set FinalBlockId on at least the last segment
252 * 7. sign the Data packets
253 * 8. send the signed Data packets
254 *
255 * As an optimization, a Data packet may be sent as soon as enough octets have been collected
256 * through StatusDatasetAppend calls.
257 */
258 void
259 addStatusDataset(const PartialName& relPrefix,
Junxiao Shif65a3362015-09-06 20:54:54 -0700260 const Authorization& authorization,
261 const StatusDatasetHandler& handler);
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700262
263public: // NotificationStream
264 /** \brief register a NotificationStream
265 * \param relPrefix a prefix for this notification stream, e.g., "faces/events";
266 * relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be
267 * non-overlapping
268 * (no relPrefix is a prefix of another relPrefix)
269 * \return a function into which notifications can be posted
270 * \pre no top-level prefix has been added
271 * \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix
272 * \throw std::domain_error one or more top-level prefix has been added
273 *
274 * Procedure for posting a notification:
275 * 1. if no top-level prefix has been added, or more than one top-level prefixes have been
276 * added,
277 * abort these steps and log an error
278 * 2. assign the next sequence number to the notification
279 * 3. place the notification block into one Data packet under the sole top-level prefix
280 * 4. sign the Data packet
281 * 5. if the Data packet is too large, abort these steps and log an error
282 * 6. send the signed Data packet
283 */
284 PostNotification
285 addNotificationStream(const PartialName& relPrefix);
286
287private:
288 typedef std::function<void(const Name& prefix,
289 const Interest& interest)> InterestHandler;
290
291 typedef std::function<void(const std::string& requester,
292 const Name& prefix,
293 const Interest& interest,
294 const ControlParameters*)> AuthorizationAcceptedCallback;
295
296 typedef std::function<void(RejectReply act,
297 const Interest& interest)> AuthorizationRejectedCallback;
298
299 /**
300 * @brief the parser of extracting control parameters from name component.
301 *
302 * @param component name component that may encode control parameters.
303 * @return a shared pointer to the extracted control parameters.
304 * @throw tlv::Error if the NameComponent cannot be parsed as ControlParameters
305 */
306 typedef std::function<shared_ptr<ControlParameters>(const name::Component& component)>
307 ControlParametersParser;
308
309 bool
310 isOverlappedWithOthers(const PartialName& relPrefix);
311
312 /**
313 * @brief process unauthorized request
314 *
315 * @param act action to reply
316 * @param interest the incoming Interest
317 */
318 void
319 afterAuthorizationRejected(RejectReply act, const Interest& interest);
320
Yanbiao Li4b4f7542016-03-11 02:04:43 +0800321 /**
322 * @brief query Data the in-memory storage by a given Interest
323 *
324 * if the query fails, invoke @p missContinuation to process @p interest.
325 *
326 * @param prefix the top-level prefix
327 * @param interest the request
328 * @param missContinuation the handler of request when the query fails
329 */
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700330 void
Yanbiao Li4b4f7542016-03-11 02:04:43 +0800331 queryStorage(const Name& prefix, const Interest& interest, const InterestHandler& missContinuation);
332
333 enum class SendDestination {
334 NONE = 0,
335 FACE = 1,
336 IMS = 2,
337 FACE_AND_IMS = 3
338 };
339
340 /**
341 * @brief send data to the face or in-memory storage
342 *
343 * create a data packet with the given @p dataName, @p content, and @p metaInfo,
344 * set its FreshnessPeriod to DEFAULT_FRESHNESS_PERIOD, and then send it out through the face and/or
345 * insert it into the in-memory storage as specified in @p option.
346 *
347 * if it's toward the in-memory storage, set its CachePolicy to NO_CACHE and limit
348 * its FreshnessPeriod in the storage as @p imsFresh
349 *
350 * @param dataName the name of this piece of data
351 * @param content the content of this piece of data
352 * @param metaInfo some meta information of this piece of data
353 * @param destination where to send this piece of data
354 * @param imsFresh freshness period of this piece of data in in-memory storage
355 */
356 void
357 sendData(const Name& dataName, const Block& content, const MetaInfo& metaInfo,
358 SendDestination destination, time::milliseconds imsFresh);
359
360 /**
361 * @brief send out a data packt through the face
362 *
363 * @param data the data packet to insert
364 */
365 void
366 sendOnFace(const Data& data);
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700367
368 /**
369 * @brief process the control-command Interest before authorization.
370 *
371 * @param prefix the top-level prefix
372 * @param relPrefix the relative prefix
373 * @param interest the incoming Interest
374 * @param parser to extract control parameters from the \p interest
375 * @param authorization to process validation on this command
376 * @param accepted the callback for successful authorization
377 * @param rejected the callback for failed authorization
378 */
379 void
380 processControlCommandInterest(const Name& prefix,
381 const Name& relPrefix,
382 const Interest& interest,
383 const ControlParametersParser& parser,
384 const Authorization& authorization,
385 const AuthorizationAcceptedCallback& accepted,
386 const AuthorizationRejectedCallback& rejected);
387
388 /**
389 * @brief process the authorized control-command.
390 *
391 * @param requester the requester
392 * @param prefix the top-level prefix
393 * @param interest the incoming Interest
394 * @param parameters control parameters of this command
395 * @param validate to validate control parameters
396 * @param handler to process this command
397 */
398 void
399 processAuthorizedControlCommandInterest(const std::string& requester,
400 const Name& prefix,
401 const Interest& interest,
402 const ControlParameters* parameters,
403 const ValidateParameters& validate,
404 const ControlCommandHandler& handler);
405
406 void
407 sendControlResponse(const ControlResponse& resp, const Interest& interest, bool isNack = false);
408
409 /**
410 * @brief process the status-dataset Interest before authorization.
411 *
412 * @param prefix the top-level prefix
413 * @param interest the incoming Interest
414 * @param authorization to process verification
415 * @param accepted callback for successful authorization
416 * @param rejected callback for failed authorization
417 */
418 void
419 processStatusDatasetInterest(const Name& prefix,
420 const Interest& interest,
421 const Authorization& authorization,
422 const AuthorizationAcceptedCallback& accepted,
423 const AuthorizationRejectedCallback& rejected);
424
425 /**
426 * @brief process the authorized status-dataset request
427 *
428 * @param requester the requester
429 * @param prefix the top-level prefix
430 * @param interest the incoming Interest
431 * @param handler to process this request
432 */
433 void
434 processAuthorizedStatusDatasetInterest(const std::string& requester,
435 const Name& prefix,
436 const Interest& interest,
437 const StatusDatasetHandler& handler);
438
Yanbiao Li4b4f7542016-03-11 02:04:43 +0800439 /**
440 * @brief send a segment of StatusDataset
441 *
442 * @param dataName the name of this piece of data
443 * @param content the content of this piece of data
444 * @param imsFresh the freshness period of this piece of data in the in-memory storage
445 * @param isFinalBlock indicates whether this piece of data is the final block
446 */
447 void
448 sendStatusDatasetSegment(const Name& dataName, const Block& content,
449 time::milliseconds imsFresh, bool isFinalBlock);
450
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700451 void
452 postNotification(const Block& notification, const PartialName& relPrefix);
453
454private:
455 struct TopPrefixEntry
456 {
457 Name topPrefix;
458 bool wantRegister;
459 const ndn::RegisteredPrefixId* registerPrefixId;
460 std::vector<const ndn::InterestFilterId*> interestFilters;
461 };
462 std::unordered_map<Name, TopPrefixEntry> m_topLevelPrefixes;
463
464 Face& m_face;
Alexander Afanasyev4c9a3d52017-01-03 17:45:19 -0800465 security::v1::KeyChain& m_keyChain;
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700466 security::SigningInfo m_signingInfo;
467
468 typedef std::unordered_map<PartialName, InterestHandler> HandlerMap;
469 typedef HandlerMap::iterator HandlerMapIt;
470 HandlerMap m_handlers;
471
472 // NotificationStream name => next sequence number
473 std::unordered_map<Name, uint64_t> m_streams;
Yanbiao Li4b4f7542016-03-11 02:04:43 +0800474
475NDN_CXX_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
476 util::InMemoryStorageFifo m_storage;
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700477};
478
479template<typename CP>
480void
481Dispatcher::addControlCommand(const PartialName& relPrefix,
Junxiao Shif65a3362015-09-06 20:54:54 -0700482 const Authorization& authorization,
483 const ValidateParameters& validateParams,
484 const ControlCommandHandler& handler)
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700485{
486 if (!m_topLevelPrefixes.empty()) {
487 throw std::domain_error("one or more top-level prefix has been added");
488 }
489
490 if (isOverlappedWithOthers(relPrefix)) {
491 throw std::out_of_range("relPrefix overlaps with another relPrefix");
492 }
493
494 ControlParametersParser parser =
495 [] (const name::Component& component) -> shared_ptr<ControlParameters> {
496 return make_shared<CP>(component.blockFromValue());
497 };
498
499 AuthorizationAcceptedCallback accepted =
500 bind(&Dispatcher::processAuthorizedControlCommandInterest, this,
501 _1, _2, _3, _4, validateParams, handler);
502
503 AuthorizationRejectedCallback rejected =
504 bind(&Dispatcher::afterAuthorizationRejected, this, _1, _2);
505
506 m_handlers[relPrefix] = bind(&Dispatcher::processControlCommandInterest, this,
507 _1, relPrefix, _2, parser, authorization, accepted, rejected);
508}
509
510} // namespace mgmt
511} // namespace ndn
512#endif // NDN_MGMT_DISPATCHER_HPP