blob: 1a8822988615116d3e01a4133e79181be5c264d6 [file] [log] [blame]
Junxiao Shi38f4ce92016-08-04 10:01:52 +00001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
Eric Newberry84d3adc2017-08-09 23:31:40 -04002/*
Junxiao Shi1f481fa2017-01-26 15:14:43 +00003 * Copyright (c) 2014-2017, Regents of the University of California,
Junxiao Shi38f4ce92016-08-04 10:01:52 +00004 * 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
Junxiao Shi38f4ce92016-08-04 10:01:52 +000026#include "face-module.hpp"
Junxiao Shi05dd4442017-02-06 22:50:07 +000027#include "find-face.hpp"
Junxiao Shi38f4ce92016-08-04 10:01:52 +000028#include "format-helpers.hpp"
29
30namespace nfd {
31namespace tools {
Junxiao Shi331ade72016-08-19 14:07:19 +000032namespace nfdc {
Junxiao Shi38f4ce92016-08-04 10:01:52 +000033
34void
Junxiao Shi1f481fa2017-01-26 15:14:43 +000035FaceModule::registerCommands(CommandParser& parser)
36{
Junxiao Shi36e54292017-02-17 18:43:16 +000037 CommandDefinition defFaceList("face", "list");
38 defFaceList
39 .setTitle("print face list")
40 .addArg("remote", ArgValueType::FACE_URI, Required::NO, Positional::YES)
41 .addArg("local", ArgValueType::FACE_URI, Required::NO, Positional::NO)
42 .addArg("scheme", ArgValueType::STRING, Required::NO, Positional::NO, "scheme");
43 parser.addCommand(defFaceList, &FaceModule::list);
44
Junxiao Shi1f481fa2017-01-26 15:14:43 +000045 CommandDefinition defFaceShow("face", "show");
46 defFaceShow
47 .setTitle("show face information")
48 .addArg("id", ArgValueType::UNSIGNED, Required::YES, Positional::YES);
49 parser.addCommand(defFaceShow, &FaceModule::show);
Junxiao Shi1d7fef52017-02-02 05:33:14 +000050
51 CommandDefinition defFaceCreate("face", "create");
52 defFaceCreate
53 .setTitle("create a face")
54 .addArg("remote", ArgValueType::FACE_URI, Required::YES, Positional::YES)
Junxiao Shi0d976922017-04-01 14:35:21 +000055 .addArg("persistency", ArgValueType::FACE_PERSISTENCY, Required::NO, Positional::YES)
Eric Newberry84d3adc2017-08-09 23:31:40 -040056 .addArg("local", ArgValueType::FACE_URI, Required::NO, Positional::NO)
57 .addArg("reliability", ArgValueType::BOOLEAN, Required::NO, Positional::NO);
Junxiao Shi1d7fef52017-02-02 05:33:14 +000058 parser.addCommand(defFaceCreate, &FaceModule::create);
Junxiao Shi05dd4442017-02-06 22:50:07 +000059
60 CommandDefinition defFaceDestroy("face", "destroy");
61 defFaceDestroy
62 .setTitle("destroy a face")
63 .addArg("face", ArgValueType::FACE_ID_OR_URI, Required::YES, Positional::YES);
64 parser.addCommand(defFaceDestroy, &FaceModule::destroy);
Junxiao Shi1f481fa2017-01-26 15:14:43 +000065}
66
67void
Junxiao Shi36e54292017-02-17 18:43:16 +000068FaceModule::list(ExecuteContext& ctx)
69{
70 auto remoteUri = ctx.args.getOptional<FaceUri>("remote");
71 auto localUri = ctx.args.getOptional<FaceUri>("local");
72 auto uriScheme = ctx.args.getOptional<std::string>("scheme");
73
74 FaceQueryFilter filter;
75 if (remoteUri) {
76 filter.setRemoteUri(remoteUri->toString());
77 }
78 if (localUri) {
79 filter.setLocalUri(localUri->toString());
80 }
81 if (uriScheme) {
82 filter.setUriScheme(*uriScheme);
83 }
84
85 FindFace findFace(ctx);
86 FindFace::Code res = findFace.execute(filter, true);
87
88 ctx.exitCode = static_cast<int>(res);
89 switch (res) {
90 case FindFace::Code::OK:
91 for (const FaceStatus& item : findFace.getResults()) {
92 formatItemText(ctx.out, item, false);
93 ctx.out << '\n';
94 }
95 break;
96 case FindFace::Code::ERROR:
97 case FindFace::Code::NOT_FOUND:
98 case FindFace::Code::CANONIZE_ERROR:
99 ctx.err << findFace.getErrorReason() << '\n';
100 break;
101 default:
102 BOOST_ASSERT_MSG(false, "unexpected FindFace result");
103 break;
104 }
105}
106
107void
Junxiao Shi1f481fa2017-01-26 15:14:43 +0000108FaceModule::show(ExecuteContext& ctx)
109{
110 uint64_t faceId = ctx.args.get<uint64_t>("id");
111
Junxiao Shi8f803f22017-02-10 03:04:28 +0000112 FindFace findFace(ctx);
113 FindFace::Code res = findFace.execute(faceId);
Junxiao Shi1d7fef52017-02-02 05:33:14 +0000114
Junxiao Shi8f803f22017-02-10 03:04:28 +0000115 ctx.exitCode = static_cast<int>(res);
116 switch (res) {
117 case FindFace::Code::OK:
118 formatItemText(ctx.out, findFace.getFaceStatus(), true);
119 break;
120 case FindFace::Code::ERROR:
121 case FindFace::Code::NOT_FOUND:
122 ctx.err << findFace.getErrorReason() << '\n';
123 break;
124 default:
125 BOOST_ASSERT_MSG(false, "unexpected FindFace result");
126 break;
127 }
Junxiao Shi1d7fef52017-02-02 05:33:14 +0000128}
129
Yanbiao Li58ba3f92017-02-15 14:27:18 +0000130/** \brief order persistency in NONE < ON_DEMAND < PERSISTENCY < PERMANENT
131 */
132static bool
133persistencyLessThan(FacePersistency x, FacePersistency y)
134{
135 switch (x) {
136 case FacePersistency::FACE_PERSISTENCY_NONE:
137 return y != FacePersistency::FACE_PERSISTENCY_NONE;
138 case FacePersistency::FACE_PERSISTENCY_ON_DEMAND:
139 return y == FacePersistency::FACE_PERSISTENCY_PERSISTENT ||
140 y == FacePersistency::FACE_PERSISTENCY_PERMANENT;
141 case FacePersistency::FACE_PERSISTENCY_PERSISTENT:
142 return y == FacePersistency::FACE_PERSISTENCY_PERMANENT;
143 case FacePersistency::FACE_PERSISTENCY_PERMANENT:
144 return false;
145 }
146 return static_cast<int>(x) < static_cast<int>(y);
147}
148
Junxiao Shi1d7fef52017-02-02 05:33:14 +0000149void
150FaceModule::create(ExecuteContext& ctx)
151{
Junxiao Shi0d976922017-04-01 14:35:21 +0000152 auto remoteUri = ctx.args.get<FaceUri>("remote");
153 auto localUri = ctx.args.getOptional<FaceUri>("local");
Junxiao Shi1d7fef52017-02-02 05:33:14 +0000154 auto persistency = ctx.args.get<FacePersistency>("persistency", FacePersistency::FACE_PERSISTENCY_PERSISTENT);
Eric Newberry84d3adc2017-08-09 23:31:40 -0400155 auto lpReliability = ctx.args.getTribool("reliability");
156
Junxiao Shi0d976922017-04-01 14:35:21 +0000157 FaceUri canonicalRemote;
158 ndn::optional<FaceUri> canonicalLocal;
159
160 auto handleCanonizeError = [&] (const FaceUri& faceUri, const std::string& error) {
161 ctx.exitCode = 4;
162 ctx.err << "Error when canonizing '" << faceUri << "': " << error << '\n';
163 };
Yanbiao Li58ba3f92017-02-15 14:27:18 +0000164
165 auto printPositiveResult = [&] (const std::string& actionSummary, const ControlParameters& resp) {
166 text::ItemAttributes ia;
167 ctx.out << actionSummary << ' '
168 << ia("id") << resp.getFaceId()
Junxiao Shi1cce2a32017-05-02 02:39:55 +0000169 << ia("local") << resp.getLocalUri()
170 << ia("remote") << resp.getUri()
Yanbiao Li58ba3f92017-02-15 14:27:18 +0000171 << ia("persistency") << resp.getFacePersistency()
Eric Newberry84d3adc2017-08-09 23:31:40 -0400172 << ia("reliability") << (resp.getFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED) ? "on" : "off")
Yanbiao Li58ba3f92017-02-15 14:27:18 +0000173 << '\n';
Yanbiao Li58ba3f92017-02-15 14:27:18 +0000174 };
175
Eric Newberry84d3adc2017-08-09 23:31:40 -0400176 auto updateFace = [&printPositiveResult] (ControlParameters respParams, ControlParameters resp) {
177 // faces/update response does not have FaceUris, copy from faces/create response
178 resp.setLocalUri(respParams.getLocalUri())
179 .setUri(respParams.getUri());
180 printPositiveResult("face-updated", resp);
181 };
182
Yanbiao Li58ba3f92017-02-15 14:27:18 +0000183 auto handle409 = [&] (const ControlResponse& resp) {
184 ControlParameters respParams(resp.getBody());
Junxiao Shi0d976922017-04-01 14:35:21 +0000185 if (respParams.getUri() != canonicalRemote.toString()) {
Yanbiao Li58ba3f92017-02-15 14:27:18 +0000186 // we are conflicting with a different face, which is a general error
187 return false;
188 }
189
190 if (persistencyLessThan(respParams.getFacePersistency(), persistency)) {
191 // need to upgrade persistency
Eric Newberry84d3adc2017-08-09 23:31:40 -0400192 ControlParameters params;
193 params.setFaceId(respParams.getFaceId()).setFacePersistency(persistency);
194 if (!boost::logic::indeterminate(lpReliability)) {
195 params.setFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED, lpReliability);
196 }
Yanbiao Li58ba3f92017-02-15 14:27:18 +0000197 ctx.controller.start<ndn::nfd::FaceUpdateCommand>(
Eric Newberry84d3adc2017-08-09 23:31:40 -0400198 params,
199 bind(updateFace, respParams, _1),
Yanbiao Li58ba3f92017-02-15 14:27:18 +0000200 ctx.makeCommandFailureHandler("upgrading face persistency"),
201 ctx.makeCommandOptions());
202 }
Eric Newberry84d3adc2017-08-09 23:31:40 -0400203 else if (lpReliability && !respParams.getFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED)) {
204 // enable reliability
205 ControlParameters params;
206 params.setFaceId(respParams.getFaceId()).setFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED, true);
207 ctx.controller.start<ndn::nfd::FaceUpdateCommand>(
208 params,
209 bind(updateFace, respParams, _1),
210 ctx.makeCommandFailureHandler("enabling reliability"),
211 ctx.makeCommandOptions());
212 }
213 else if (!lpReliability && respParams.getFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED)) {
214 // disable reliability
215 ControlParameters params;
216 params.setFaceId(respParams.getFaceId()).setFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED, false);
217 ctx.controller.start<ndn::nfd::FaceUpdateCommand>(
218 params,
219 bind(updateFace, respParams, _1),
220 ctx.makeCommandFailureHandler("disabling reliability"),
221 ctx.makeCommandOptions());
222 }
Yanbiao Li58ba3f92017-02-15 14:27:18 +0000223 else {
Eric Newberry84d3adc2017-08-09 23:31:40 -0400224 // don't do anything
Yanbiao Li58ba3f92017-02-15 14:27:18 +0000225 printPositiveResult("face-exists", respParams);
226 }
227 return true;
228 };
Junxiao Shi1d7fef52017-02-02 05:33:14 +0000229
Junxiao Shi0d976922017-04-01 14:35:21 +0000230 auto doCreateFace = [&] {
231 ControlParameters params;
232 params.setUri(canonicalRemote.toString());
233 if (canonicalLocal) {
234 params.setLocalUri(canonicalLocal->toString());
235 }
236 params.setFacePersistency(persistency);
Eric Newberry84d3adc2017-08-09 23:31:40 -0400237 if (!boost::logic::indeterminate(lpReliability)) {
238 params.setFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED, lpReliability);
239 }
Junxiao Shi0d976922017-04-01 14:35:21 +0000240
241 ctx.controller.start<ndn::nfd::FaceCreateCommand>(
242 params,
243 bind(printPositiveResult, "face-created", _1),
244 [&] (const ControlResponse& resp) {
245 if (resp.getCode() == 409 && handle409(resp)) {
246 return;
247 }
248 ctx.makeCommandFailureHandler("creating face")(resp); // invoke general error handler
249 },
250 ctx.makeCommandOptions());
251 };
252
253 remoteUri.canonize(
254 [&] (const FaceUri& canonicalUri) {
255 canonicalRemote = canonicalUri;
256 if (localUri) {
257 localUri->canonize(
258 [&] (const FaceUri& canonicalUri) {
259 canonicalLocal = canonicalUri;
260 doCreateFace();
261 },
262 bind(handleCanonizeError, *localUri, _1),
263 ctx.face.getIoService(), ctx.getTimeout());
264 }
265 else {
266 doCreateFace();
267 }
Junxiao Shi1d7fef52017-02-02 05:33:14 +0000268 },
Junxiao Shi0d976922017-04-01 14:35:21 +0000269 bind(handleCanonizeError, remoteUri, _1),
Junxiao Shi1d7fef52017-02-02 05:33:14 +0000270 ctx.face.getIoService(), ctx.getTimeout());
Junxiao Shi1f481fa2017-01-26 15:14:43 +0000271
272 ctx.face.processEvents();
273}
274
275void
Junxiao Shi05dd4442017-02-06 22:50:07 +0000276FaceModule::destroy(ExecuteContext& ctx)
277{
Junxiao Shi918e5d42017-02-25 03:58:21 +0000278 const boost::any& faceIdOrUri = ctx.args.at("face");
Junxiao Shi05dd4442017-02-06 22:50:07 +0000279
280 FindFace findFace(ctx);
Junxiao Shi918e5d42017-02-25 03:58:21 +0000281 FindFace::Code res = findFace.execute(faceIdOrUri);
Junxiao Shi05dd4442017-02-06 22:50:07 +0000282
283 ctx.exitCode = static_cast<int>(res);
284 switch (res) {
285 case FindFace::Code::OK:
286 break;
287 case FindFace::Code::ERROR:
288 case FindFace::Code::CANONIZE_ERROR:
289 case FindFace::Code::NOT_FOUND:
290 ctx.err << findFace.getErrorReason() << '\n';
291 return;
292 case FindFace::Code::AMBIGUOUS:
293 ctx.err << "Multiple faces match specified remote FaceUri. Re-run the command with a FaceId:";
294 findFace.printDisambiguation(ctx.err, FindFace::DisambiguationStyle::LOCAL_URI);
295 ctx.err << '\n';
296 return;
297 default:
298 BOOST_ASSERT_MSG(false, "unexpected FindFace result");
299 return;
300 }
301
302 const FaceStatus& face = findFace.getFaceStatus();
303
304 ctx.controller.start<ndn::nfd::FaceDestroyCommand>(
305 ControlParameters().setFaceId(face.getFaceId()),
306 [&] (const ControlParameters& resp) {
307 ctx.out << "face-destroyed ";
308 text::ItemAttributes ia;
309 ctx.out << ia("id") << face.getFaceId()
310 << ia("local") << face.getLocalUri()
311 << ia("remote") << face.getRemoteUri()
Eric Newberry84d3adc2017-08-09 23:31:40 -0400312 << ia("persistency") << face.getFacePersistency()
313 << ia("reliability") << (resp.getFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED) ? "on" : "off")
314 << '\n';
Junxiao Shi05dd4442017-02-06 22:50:07 +0000315 },
316 ctx.makeCommandFailureHandler("destroying face"),
317 ctx.makeCommandOptions());
318
319 ctx.face.processEvents();
320}
321
322void
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000323FaceModule::fetchStatus(Controller& controller,
324 const function<void()>& onSuccess,
Junxiao Shi29b41282016-08-22 03:47:02 +0000325 const Controller::DatasetFailCallback& onFailure,
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000326 const CommandOptions& options)
327{
328 controller.fetch<ndn::nfd::FaceDataset>(
329 [this, onSuccess] (const std::vector<FaceStatus>& result) {
330 m_status = result;
331 onSuccess();
332 },
333 onFailure, options);
334}
335
336void
337FaceModule::formatStatusXml(std::ostream& os) const
338{
339 os << "<faces>";
340 for (const FaceStatus& item : m_status) {
341 this->formatItemXml(os, item);
342 }
343 os << "</faces>";
344}
345
346void
347FaceModule::formatItemXml(std::ostream& os, const FaceStatus& item) const
348{
349 os << "<face>";
350
351 os << "<faceId>" << item.getFaceId() << "</faceId>";
352 os << "<remoteUri>" << xml::Text{item.getRemoteUri()} << "</remoteUri>";
353 os << "<localUri>" << xml::Text{item.getLocalUri()} << "</localUri>";
354
355 if (item.hasExpirationPeriod()) {
356 os << "<expirationPeriod>" << xml::formatDuration(item.getExpirationPeriod())
357 << "</expirationPeriod>";
358 }
359 os << "<faceScope>" << item.getFaceScope() << "</faceScope>";
360 os << "<facePersistency>" << item.getFacePersistency() << "</facePersistency>";
361 os << "<linkType>" << item.getLinkType() << "</linkType>";
362
Eric Newberry6d932e82016-11-24 05:05:43 +0000363 if (item.getFlags() == 0) {
364 os << "<flags/>";
365 }
366 else {
367 os << "<flags>";
368 if (item.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED)) {
369 os << "<localFieldsEnabled/>";
370 }
Eric Newberry84d3adc2017-08-09 23:31:40 -0400371 if (item.getFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED)) {
372 os << "<lpReliabilityEnabled/>";
373 }
Eric Newberry6d932e82016-11-24 05:05:43 +0000374 os << "</flags>";
375 }
376
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000377 os << "<packetCounters>";
378 os << "<incomingPackets>"
379 << "<nInterests>" << item.getNInInterests() << "</nInterests>"
Junxiao Shif03d4792017-04-06 16:41:22 +0000380 << "<nData>" << item.getNInData() << "</nData>"
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000381 << "<nNacks>" << item.getNInNacks() << "</nNacks>"
382 << "</incomingPackets>";
383 os << "<outgoingPackets>"
384 << "<nInterests>" << item.getNOutInterests() << "</nInterests>"
Junxiao Shif03d4792017-04-06 16:41:22 +0000385 << "<nData>" << item.getNOutData() << "</nData>"
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000386 << "<nNacks>" << item.getNOutNacks() << "</nNacks>"
387 << "</outgoingPackets>";
388 os << "</packetCounters>";
389
390 os << "<byteCounters>";
391 os << "<incomingBytes>" << item.getNInBytes() << "</incomingBytes>";
392 os << "<outgoingBytes>" << item.getNOutBytes() << "</outgoingBytes>";
393 os << "</byteCounters>";
394
395 os << "</face>";
396}
397
398void
399FaceModule::formatStatusText(std::ostream& os) const
400{
401 os << "Faces:\n";
402 for (const FaceStatus& item : m_status) {
Junxiao Shi1f481fa2017-01-26 15:14:43 +0000403 os << " ";
404 formatItemText(os, item, false);
405 os << '\n';
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000406 }
407}
408
409void
Junxiao Shi1f481fa2017-01-26 15:14:43 +0000410FaceModule::formatItemText(std::ostream& os, const FaceStatus& item, bool wantMultiLine)
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000411{
Junxiao Shi1f481fa2017-01-26 15:14:43 +0000412 text::ItemAttributes ia(wantMultiLine, 8);
413
414 os << ia("faceid") << item.getFaceId();
415 os << ia("remote") << item.getRemoteUri();
416 os << ia("local") << item.getLocalUri();
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000417
418 if (item.hasExpirationPeriod()) {
Junxiao Shi1f481fa2017-01-26 15:14:43 +0000419 os << ia("expires") << text::formatDuration(item.getExpirationPeriod());
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000420 }
421
Junxiao Shi1f481fa2017-01-26 15:14:43 +0000422 os << ia("counters")
423 << "{in={"
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000424 << item.getNInInterests() << "i "
Junxiao Shif03d4792017-04-06 16:41:22 +0000425 << item.getNInData() << "d "
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000426 << item.getNInNacks() << "n "
Junxiao Shi1f481fa2017-01-26 15:14:43 +0000427 << item.getNInBytes() << "B} "
428 << "out={"
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000429 << item.getNOutInterests() << "i "
Junxiao Shif03d4792017-04-06 16:41:22 +0000430 << item.getNOutData() << "d "
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000431 << item.getNOutNacks() << "n "
432 << item.getNOutBytes() << "B}}";
433
Junxiao Shi1f481fa2017-01-26 15:14:43 +0000434 os << ia("flags") << '{';
435 text::Separator flagSep("", " ");
436 os << flagSep << item.getFaceScope();
437 os << flagSep << item.getFacePersistency();
438 os << flagSep << item.getLinkType();
Eric Newberry6d932e82016-11-24 05:05:43 +0000439 if (item.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED)) {
Junxiao Shi1f481fa2017-01-26 15:14:43 +0000440 os << flagSep << "local-fields";
Eric Newberry6d932e82016-11-24 05:05:43 +0000441 }
Eric Newberry84d3adc2017-08-09 23:31:40 -0400442 if (item.getFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED)) {
443 os << flagSep << "lp-reliability";
444 }
Junxiao Shi1f481fa2017-01-26 15:14:43 +0000445 os << '}';
Eric Newberry6d932e82016-11-24 05:05:43 +0000446
Junxiao Shi1f481fa2017-01-26 15:14:43 +0000447 os << ia.end();
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000448}
449
Junxiao Shi331ade72016-08-19 14:07:19 +0000450} // namespace nfdc
Junxiao Shi38f4ce92016-08-04 10:01:52 +0000451} // namespace tools
452} // namespace nfd