blob: 922722fed0073e787714610da5663f6f97bb5f74 [file] [log] [blame]
Yanbiao Li8ee37ed2015-05-19 12:44:04 -07001/* -*- 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
38namespace ndn {
39namespace 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 */
48typedef std::function<void(const std::string& requester)> AcceptContinuation;
49
50/** \brief indicate how to reply in case authorization is rejected
51 */
52enum 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 */
63typedef 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 */
77typedef 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 */
84Authorization
85makeAcceptAllAuthorization();
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 */
93typedef 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 */
98typedef 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 */
107typedef 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 */
119typedef 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 */
126typedef std::function<void(const Block& notification)> PostNotification;
127
128// ---- DISPATCHER ----
129
130/** \brief represents a dispatcher on server side of NFD Management protocol
131 */
132class 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
144public:
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
193public: // 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
224public: // 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
260public: // 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
284private:
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
396private:
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
418template<typename CP>
419void
420Dispatcher::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