blob: 7c6121d47a06cfd139e4438365b3582f41c3365c [file] [log] [blame]
Yanbiao Li8ee37ed2015-05-19 12:44:04 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
Alexander Afanasyev80b68e12015-09-17 17:01:04 -07003 * Copyright (c) 2013-2015 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"
28#include "control-response.hpp"
29#include "control-parameters.hpp"
30#include "status-dataset-context.hpp"
31
32#include <unordered_map>
33
34namespace ndn {
35namespace 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 */
44typedef std::function<void(const std::string& requester)> AcceptContinuation;
45
46/** \brief indicate how to reply in case authorization is rejected
47 */
48enum 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 */
59typedef 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 */
73typedef std::function<void(const Name& prefix, const Interest& interest,
74 const ControlParameters* params,
Junxiao Shif65a3362015-09-06 20:54:54 -070075 const AcceptContinuation& accept,
76 const RejectContinuation& reject)> Authorization;
Yanbiao Li8ee37ed2015-05-19 12:44:04 -070077
78/** \return an Authorization that accepts all Interests, with empty string as requester
79 */
80Authorization
81makeAcceptAllAuthorization();
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 */
89typedef 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 */
94typedef 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 */
103typedef std::function<void(const Name& prefix, const Interest& interest,
104 const ControlParameters& params,
Junxiao Shif65a3362015-09-06 20:54:54 -0700105 const CommandContinuation& done)> ControlCommandHandler;
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700106
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 */
115typedef 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 */
122typedef std::function<void(const Block& notification)> PostNotification;
123
124// ---- DISPATCHER ----
125
126/** \brief represents a dispatcher on server side of NFD Management protocol
127 */
128class 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
140public:
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
189public: // 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 Shif65a3362015-09-06 20:54:54 -0700216 const Authorization& authorization,
217 const ValidateParameters& validateParams,
218 const ControlCommandHandler& handler);
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700219
220public: // 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 Shif65a3362015-09-06 20:54:54 -0700253 const Authorization& authorization,
254 const StatusDatasetHandler& handler);
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700255
256public: // 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
280private:
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
392private:
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
414template<typename CP>
415void
416Dispatcher::addControlCommand(const PartialName& relPrefix,
Junxiao Shif65a3362015-09-06 20:54:54 -0700417 const Authorization& authorization,
418 const ValidateParameters& validateParams,
419 const ControlCommandHandler& handler)
Yanbiao Li8ee37ed2015-05-19 12:44:04 -0700420{
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