face-module.cpp
Go to the documentation of this file.
1 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2 /*
3  * Copyright (c) 2014-2020, 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 #include "face-module.hpp"
27 #include "canonizer.hpp"
28 #include "find-face.hpp"
29 
30 namespace nfd {
31 namespace tools {
32 namespace nfdc {
33 
34 void
36 {
37  CommandDefinition defFaceList("face", "list");
38  defFaceList
39  .setTitle("print face list")
43  parser.addCommand(defFaceList, &FaceModule::list);
44  parser.addAlias("face", "list", "");
45 
46  CommandDefinition defFaceShow("face", "show");
47  defFaceShow
48  .setTitle("show face information")
50  parser.addCommand(defFaceShow, &FaceModule::show);
51 
52  CommandDefinition defFaceCreate("face", "create");
53  defFaceCreate
54  .setTitle("create a face")
60  .addArg("congestion-marking-interval", ArgValueType::UNSIGNED, Required::NO, Positional::NO)
61  .addArg("default-congestion-threshold", ArgValueType::UNSIGNED, Required::NO, Positional::NO)
63  parser.addCommand(defFaceCreate, &FaceModule::create);
64 
65  CommandDefinition defFaceDestroy("face", "destroy");
66  defFaceDestroy
67  .setTitle("destroy a face")
69  parser.addCommand(defFaceDestroy, &FaceModule::destroy);
70 }
71 
72 void
74 {
75  auto remoteUri = ctx.args.getOptional<FaceUri>("remote");
76  auto localUri = ctx.args.getOptional<FaceUri>("local");
77  auto uriScheme = ctx.args.getOptional<std::string>("scheme");
78 
79  FaceQueryFilter filter;
80  if (remoteUri) {
81  filter.setRemoteUri(remoteUri->toString());
82  }
83  if (localUri) {
84  filter.setLocalUri(localUri->toString());
85  }
86  if (uriScheme) {
87  filter.setUriScheme(*uriScheme);
88  }
89 
90  FindFace findFace(ctx);
91  FindFace::Code res = findFace.execute(filter, true);
92 
93  ctx.exitCode = static_cast<int>(res);
94  switch (res) {
95  case FindFace::Code::OK:
96  for (const FaceStatus& item : findFace.getResults()) {
97  formatItemText(ctx.out, item, false);
98  ctx.out << '\n';
99  }
100  break;
104  ctx.err << findFace.getErrorReason() << '\n';
105  break;
106  default:
107  BOOST_ASSERT_MSG(false, "unexpected FindFace result");
108  break;
109  }
110 }
111 
112 void
114 {
115  uint64_t faceId = ctx.args.get<uint64_t>("id");
116 
117  FindFace findFace(ctx);
118  FindFace::Code res = findFace.execute(faceId);
119 
120  ctx.exitCode = static_cast<int>(res);
121  switch (res) {
122  case FindFace::Code::OK:
123  formatItemText(ctx.out, findFace.getFaceStatus(), true);
124  break;
127  ctx.err << findFace.getErrorReason() << '\n';
128  break;
129  default:
130  BOOST_ASSERT_MSG(false, "unexpected FindFace result");
131  break;
132  }
133 }
134 
137 static bool
138 persistencyLessThan(FacePersistency x, FacePersistency y)
139 {
140  switch (x) {
141  case FacePersistency::FACE_PERSISTENCY_NONE:
142  return y != FacePersistency::FACE_PERSISTENCY_NONE;
143  case FacePersistency::FACE_PERSISTENCY_ON_DEMAND:
144  return y == FacePersistency::FACE_PERSISTENCY_PERSISTENT ||
145  y == FacePersistency::FACE_PERSISTENCY_PERMANENT;
146  case FacePersistency::FACE_PERSISTENCY_PERSISTENT:
147  return y == FacePersistency::FACE_PERSISTENCY_PERMANENT;
148  case FacePersistency::FACE_PERSISTENCY_PERMANENT:
149  return false;
150  }
151  return static_cast<int>(x) < static_cast<int>(y);
152 }
153 
154 void
156 {
157  auto remoteUri = ctx.args.get<FaceUri>("remote");
158  auto localUri = ctx.args.getOptional<FaceUri>("local");
159  auto persistency = ctx.args.get<FacePersistency>("persistency", FacePersistency::FACE_PERSISTENCY_PERSISTENT);
160  auto lpReliability = ctx.args.getTribool("reliability");
161  auto congestionMarking = ctx.args.getTribool("congestion-marking");
162  auto baseCongestionMarkingIntervalMs = ctx.args.getOptional<uint64_t>("congestion-marking-interval");
163  auto defaultCongestionThreshold = ctx.args.getOptional<uint64_t>("default-congestion-threshold");
164  auto mtuArg = ctx.args.getOptional<std::string>("mtu");
165 
166  // MTU is nominally a uint64_t, but can be the string value 'auto' to unset an override MTU
167  optional<uint64_t> mtu;
168  if (mtuArg == "auto") {
169  mtu = std::numeric_limits<uint64_t>::max();
170  }
171  else if (mtuArg) {
172  // boost::lexical_cast<uint64_t> will accept negative numbers
173  int64_t v = -1;
174  if (!boost::conversion::try_lexical_convert<int64_t>(*mtuArg, v) || v < 0) {
175  ctx.exitCode = 2;
176  ctx.err << "MTU must either be a non-negative integer or 'auto'\n";
177  return;
178  }
179 
180  mtu = static_cast<uint64_t>(v);
181  }
182 
183  optional<FaceUri> canonicalRemote;
184  optional<FaceUri> canonicalLocal;
185 
186  auto updateFace = [&] (ControlParameters respParams, ControlParameters resp) {
187  // faces/update response does not have FaceUris, copy from faces/create response
188  resp.setLocalUri(respParams.getLocalUri())
189  .setUri(respParams.getUri());
190  printSuccess(ctx.out, "face-updated", resp);
191  };
192 
193  auto handle409 = [&] (const ControlResponse& resp) {
194  ControlParameters respParams(resp.getBody());
195  if (respParams.getUri() != canonicalRemote->toString()) {
196  // we are conflicting with a different face, which is a general error
197  return false;
198  }
199 
200  bool isChangingParams = false;
201  ControlParameters params;
202  params.setFaceId(respParams.getFaceId());
203 
204  if (mtu && (!respParams.hasMtu() || respParams.getMtu() != *mtu)) {
205  isChangingParams = true;
206  params.setMtu(*mtu);
207  }
208 
209  if (persistencyLessThan(respParams.getFacePersistency(), persistency)) {
210  isChangingParams = true;
211  params.setFacePersistency(persistency);
212  }
213 
214  if (!boost::logic::indeterminate(lpReliability) &&
215  lpReliability != respParams.getFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED)) {
216  isChangingParams = true;
217  params.setFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED, bool(lpReliability));
218  }
219 
220  if (!boost::logic::indeterminate(congestionMarking) &&
221  congestionMarking != respParams.getFlagBit(ndn::nfd::BIT_CONGESTION_MARKING_ENABLED)) {
222  isChangingParams = true;
223  params.setFlagBit(ndn::nfd::BIT_CONGESTION_MARKING_ENABLED, bool(congestionMarking));
224  }
225 
226  if (baseCongestionMarkingIntervalMs) {
227  isChangingParams = true;
228  params.setBaseCongestionMarkingInterval(time::milliseconds(*baseCongestionMarkingIntervalMs));
229  }
230 
231  if (defaultCongestionThreshold) {
232  isChangingParams = true;
233  params.setDefaultCongestionThreshold(*defaultCongestionThreshold);
234  }
235 
236  if (isChangingParams) {
237  ctx.controller.start<ndn::nfd::FaceUpdateCommand>(
238  params,
239  bind(updateFace, respParams, _1),
240  ctx.makeCommandFailureHandler("updating face"),
241  ctx.makeCommandOptions());
242  }
243  else {
244  // don't do anything
245  printSuccess(ctx.out, "face-exists", respParams);
246  }
247 
248  return true;
249  };
250 
251  auto doCreateFace = [&] {
252  ControlParameters params;
253  params.setUri(canonicalRemote->toString());
254  if (canonicalLocal) {
255  params.setLocalUri(canonicalLocal->toString());
256  }
257  params.setFacePersistency(persistency);
258  if (!boost::logic::indeterminate(lpReliability)) {
259  params.setFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED, bool(lpReliability));
260  }
261  if (!boost::logic::indeterminate(congestionMarking)) {
262  params.setFlagBit(ndn::nfd::BIT_CONGESTION_MARKING_ENABLED, bool(congestionMarking));
263  }
264  if (baseCongestionMarkingIntervalMs) {
265  params.setBaseCongestionMarkingInterval(time::milliseconds(*baseCongestionMarkingIntervalMs));
266  }
267  if (defaultCongestionThreshold) {
268  params.setDefaultCongestionThreshold(*defaultCongestionThreshold);
269  }
270  if (mtu) {
271  params.setMtu(*mtu);
272  }
273 
274  ctx.controller.start<ndn::nfd::FaceCreateCommand>(
275  params,
276  [&] (const ControlParameters& resp) {
277  printSuccess(ctx.out, "face-created", resp);
278  },
279  [&] (const ControlResponse& resp) {
280  if (resp.getCode() == 409 && handle409(resp)) {
281  return;
282  }
283  ctx.makeCommandFailureHandler("creating face")(resp); // invoke general error handler
284  },
285  ctx.makeCommandOptions());
286  };
287 
288  std::string error;
289  std::tie(canonicalRemote, error) = canonize(ctx, remoteUri);
290  if (canonicalRemote) {
291  // RemoteUri canonization successful
292  if (localUri) {
293  std::tie(canonicalLocal, error) = canonize(ctx, *localUri);
294  if (canonicalLocal) {
295  // LocalUri canonization successful
296  doCreateFace();
297  }
298  else {
299  // LocalUri canonization failure
300  auto canonizationError = canonizeErrorHelper(*localUri, error, "local FaceUri");
301  ctx.exitCode = static_cast<int>(canonizationError.first);
302  ctx.err << canonizationError.second << '\n';
303  }
304  }
305  else {
306  doCreateFace();
307  }
308  }
309  else {
310  // RemoteUri canonization failure
311  auto canonizationError = canonizeErrorHelper(remoteUri, error, "remote FaceUri");
312  ctx.exitCode = static_cast<int>(canonizationError.first);
313  ctx.err << canonizationError.second << '\n';
314  }
315 
316  ctx.face.processEvents();
317 }
318 
319 void
321 {
322  FindFace findFace(ctx);
323  FindFace::Code res = findFace.execute(ctx.args.at("face"));
324 
325  ctx.exitCode = static_cast<int>(res);
326  switch (res) {
327  case FindFace::Code::OK:
328  break;
332  ctx.err << findFace.getErrorReason() << '\n';
333  return;
335  ctx.err << "Multiple faces match specified remote FaceUri. Re-run the command with a FaceId:";
337  ctx.err << '\n';
338  return;
339  default:
340  BOOST_ASSERT_MSG(false, "unexpected FindFace result");
341  return;
342  }
343 
344  const FaceStatus& face = findFace.getFaceStatus();
345 
346  ctx.controller.start<ndn::nfd::FaceDestroyCommand>(
347  ControlParameters().setFaceId(face.getFaceId()),
348  [&] (const ControlParameters& resp) {
349  // We can't use printSuccess because some face attributes come from FaceStatus not ControlResponse
350  ctx.out << "face-destroyed ";
352  ctx.out << ia("id") << face.getFaceId()
353  << ia("local") << face.getLocalUri()
354  << ia("remote") << face.getRemoteUri()
355  << ia("persistency") << face.getFacePersistency();
356  printFaceParams(ctx.out, ia, resp);
357  },
358  ctx.makeCommandFailureHandler("destroying face"),
359  ctx.makeCommandOptions());
360 
361  ctx.face.processEvents();
362 }
363 
364 void
365 FaceModule::fetchStatus(Controller& controller,
366  const std::function<void()>& onSuccess,
367  const Controller::DatasetFailCallback& onFailure,
368  const CommandOptions& options)
369 {
370  controller.fetch<ndn::nfd::FaceDataset>(
371  [this, onSuccess] (const std::vector<FaceStatus>& result) {
372  m_status = result;
373  onSuccess();
374  },
375  onFailure, options);
376 }
377 
378 void
379 FaceModule::formatStatusXml(std::ostream& os) const
380 {
381  os << "<faces>";
382  for (const FaceStatus& item : m_status) {
383  this->formatItemXml(os, item);
384  }
385  os << "</faces>";
386 }
387 
388 void
389 FaceModule::formatItemXml(std::ostream& os, const FaceStatus& item) const
390 {
391  os << "<face>";
392 
393  os << "<faceId>" << item.getFaceId() << "</faceId>";
394  os << "<remoteUri>" << xml::Text{item.getRemoteUri()} << "</remoteUri>";
395  os << "<localUri>" << xml::Text{item.getLocalUri()} << "</localUri>";
396 
397  if (item.hasExpirationPeriod()) {
398  os << "<expirationPeriod>" << xml::formatDuration(item.getExpirationPeriod())
399  << "</expirationPeriod>";
400  }
401  os << "<faceScope>" << item.getFaceScope() << "</faceScope>";
402  os << "<facePersistency>" << item.getFacePersistency() << "</facePersistency>";
403  os << "<linkType>" << item.getLinkType() << "</linkType>";
404 
405  if (!item.hasBaseCongestionMarkingInterval() && !item.hasDefaultCongestionThreshold()) {
406  os << "<congestion/>";
407  }
408  else {
409  os << "<congestion>";
410  if (item.hasBaseCongestionMarkingInterval()) {
411  os << "<baseMarkingInterval>" << xml::formatDuration(item.getBaseCongestionMarkingInterval())
412  << "</baseMarkingInterval>";
413  }
414  if (item.hasDefaultCongestionThreshold()) {
415  os << "<defaultThreshold>" << item.getDefaultCongestionThreshold() << "</defaultThreshold>";
416  }
417  os << "</congestion>";
418  }
419 
420  if (item.hasMtu()) {
421  os << "<mtu>" << item.getMtu() << "</mtu>";
422  }
423 
424  if (item.getFlags() == 0) {
425  os << "<flags/>";
426  }
427  else {
428  os << "<flags>";
429  os << xml::Flag{"localFieldsEnabled", item.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED)};
430  os << xml::Flag{"lpReliabilityEnabled", item.getFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED)};
431  os << xml::Flag{"congestionMarkingEnabled", item.getFlagBit(ndn::nfd::BIT_CONGESTION_MARKING_ENABLED)};
432  os << "</flags>";
433  }
434 
435  os << "<packetCounters>";
436  os << "<incomingPackets>"
437  << "<nInterests>" << item.getNInInterests() << "</nInterests>"
438  << "<nData>" << item.getNInData() << "</nData>"
439  << "<nNacks>" << item.getNInNacks() << "</nNacks>"
440  << "</incomingPackets>";
441  os << "<outgoingPackets>"
442  << "<nInterests>" << item.getNOutInterests() << "</nInterests>"
443  << "<nData>" << item.getNOutData() << "</nData>"
444  << "<nNacks>" << item.getNOutNacks() << "</nNacks>"
445  << "</outgoingPackets>";
446  os << "</packetCounters>";
447 
448  os << "<byteCounters>";
449  os << "<incomingBytes>" << item.getNInBytes() << "</incomingBytes>";
450  os << "<outgoingBytes>" << item.getNOutBytes() << "</outgoingBytes>";
451  os << "</byteCounters>";
452 
453  os << "</face>";
454 }
455 
456 void
457 FaceModule::formatStatusText(std::ostream& os) const
458 {
459  os << "Faces:\n";
460  for (const FaceStatus& item : m_status) {
461  os << " ";
462  formatItemText(os, item, false);
463  os << '\n';
464  }
465 }
466 
467 void
468 FaceModule::formatItemText(std::ostream& os, const FaceStatus& item, bool wantMultiLine)
469 {
470  text::ItemAttributes ia(wantMultiLine, 10);
471 
472  os << ia("faceid") << item.getFaceId();
473  os << ia("remote") << item.getRemoteUri();
474  os << ia("local") << item.getLocalUri();
475 
476  if (item.hasExpirationPeriod()) {
477  os << ia("expires") << text::formatDuration<time::seconds>(item.getExpirationPeriod());
478  }
479 
480  if (item.hasBaseCongestionMarkingInterval() || item.hasDefaultCongestionThreshold()) {
481  os << ia("congestion") << "{";
482  text::Separator congestionSep("", " ");
483  if (item.hasBaseCongestionMarkingInterval()) {
484  os << congestionSep << "base-marking-interval="
485  << text::formatDuration<time::milliseconds>(item.getBaseCongestionMarkingInterval());
486  }
487  if (item.hasDefaultCongestionThreshold()) {
488  os << congestionSep << "default-threshold=" << item.getDefaultCongestionThreshold() << "B";
489  }
490  os << "}";
491  }
492 
493  if (item.hasMtu()) {
494  os << ia("mtu") << item.getMtu();
495  }
496 
497  os << ia("counters")
498  << "{in={"
499  << item.getNInInterests() << "i "
500  << item.getNInData() << "d "
501  << item.getNInNacks() << "n "
502  << item.getNInBytes() << "B} "
503  << "out={"
504  << item.getNOutInterests() << "i "
505  << item.getNOutData() << "d "
506  << item.getNOutNacks() << "n "
507  << item.getNOutBytes() << "B}}";
508 
509  os << ia("flags") << '{';
510  text::Separator flagSep("", " ");
511  os << flagSep << item.getFaceScope();
512  os << flagSep << item.getFacePersistency();
513  os << flagSep << item.getLinkType();
514  if (item.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED)) {
515  os << flagSep << "local-fields";
516  }
517  if (item.getFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED)) {
518  os << flagSep << "lp-reliability";
519  }
520  if (item.getFlagBit(ndn::nfd::BIT_CONGESTION_MARKING_ENABLED)) {
521  os << flagSep << "congestion-marking";
522  }
523  os << '}';
524 
525  os << ia.end();
526 }
527 
528 void
529 FaceModule::printSuccess(std::ostream& os,
530  const std::string& actionSummary,
531  const ControlParameters& resp)
532 {
534  os << actionSummary << ' '
535  << ia("id") << resp.getFaceId()
536  << ia("local") << resp.getLocalUri()
537  << ia("remote") << resp.getUri()
538  << ia("persistency") << resp.getFacePersistency();
539  printFaceParams(os, ia, resp);
540 }
541 
542 void
543 FaceModule::printFaceParams(std::ostream& os, text::ItemAttributes& ia, const ControlParameters& resp)
544 {
545  os << ia("reliability") << text::OnOff{resp.getFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED)}
546  << ia("congestion-marking") << text::OnOff{resp.getFlagBit(ndn::nfd::BIT_CONGESTION_MARKING_ENABLED)};
547  if (resp.hasBaseCongestionMarkingInterval()) {
548  os << ia("congestion-marking-interval")
549  << text::formatDuration<time::milliseconds>(resp.getBaseCongestionMarkingInterval());
550  }
551  if (resp.hasDefaultCongestionThreshold()) {
552  os << ia("default-congestion-threshold") << resp.getDefaultCongestionThreshold() << "B";
553  }
554  if (resp.hasMtu()) {
555  os << ia("mtu") << resp.getMtu();
556  }
557  os << '\n';
558 }
559 
560 } // namespace nfdc
561 } // namespace tools
562 } // namespace nfd
const CommandArguments & args
void formatStatusText(std::ostream &os) const override
format collected status as text
std::string formatDuration(time::nanoseconds d)
Controller::CommandFailCallback makeCommandFailureHandler(const std::string &commandName)
const FaceStatus & getFaceStatus() const
Definition: find-face.cpp:166
declares semantics of a command
std::ostream & out
output stream
context for command execution
void formatStatusXml(std::ostream &os) const override
format collected status as XML
static void printFaceParams(std::ostream &os, text::ItemAttributes &ia, const ControlParameters &resp)
print face response parameters to specified ostream
found exactly one face, or found multiple faces when allowMulti is true
const std::vector< FaceStatus > & getResults() const
Definition: find-face.hpp:90
error during FaceUri canonization
static bool persistencyLessThan(FacePersistency x, FacePersistency y)
order persistency in NONE < ON_DEMAND < PERSISTENCY < PERMANENT
ndn::nfd::CommandOptions makeCommandOptions() const
CommandDefinition & setTitle(const std::string &title)
set one-line description
boost::logic::tribool getTribool(const std::string &key) const
get an optional boolean argument as tribool
print true as an empty element and false as nothing
optional< T > getOptional(const std::string &key) const
found multiple faces and allowMulti is false
static void create(ExecuteContext &ctx)
the &#39;face create&#39; command
void formatItemXml(std::ostream &os, const FaceStatus &item) const
format a single status item as XML
Copyright (c) 2014-2015, Regents of the University of California, Arizona Board of Regents...
Definition: algorithm.hpp:32
procedure to find a face
Definition: find-face.hpp:40
face persistency &#39;persistent&#39; or &#39;permanent&#39;
Code execute(const FaceUri &faceUri, bool allowMulti=false)
find face by FaceUri
Definition: find-face.cpp:44
static void registerCommands(CommandParser &parser)
register &#39;face list&#39;, &#39;face show&#39;, &#39;face create&#39;, &#39;face destroy&#39; commands
Definition: face-module.cpp:35
CommandDefinition & addArg(const std::string &name, ArgValueType valueType, Required isRequired=Required::NO, Positional allowPositional=Positional::NO, const std::string &metavar="")
declare an argument
std::pair< FindFace::Code, std::string > canonizeErrorHelper(const FaceUri &uri, const std::string &error, const std::string &field)
helper to generate exit code and error message for face canonization failures
Definition: canonizer.cpp:47
print attributes of an item
std::pair< optional< FaceUri >, std::string > canonize(ExecuteContext &ctx, const FaceUri &uri)
canonize FaceUri
Definition: canonizer.cpp:33
argument is required
static void show(ExecuteContext &ctx)
the &#39;face show&#39; command
std::ostream & err
error stream
CommandParser & addCommand(const CommandDefinition &def, const ExecuteCommand &execute, std::underlying_type< AvailableIn >::type modes=AVAILABLE_IN_ALL)
add an available command
static void formatItemText(std::ostream &os, const FaceStatus &item, bool wantMultiLine)
format a single status item as text
static void list(ExecuteContext &ctx)
the &#39;face list&#39; command
Definition: face-module.cpp:73
const std::string & getErrorReason() const
Definition: find-face.hpp:113
static void printSuccess(std::ostream &os, const std::string &actionSummary, const ControlParameters &resp)
print face action success message to specified ostream
argument is optional
T get(const std::string &key, const T &defaultValue=T()) const
static void destroy(ExecuteContext &ctx)
the &#39;face destroy&#39; command
void fetchStatus(Controller &controller, const std::function< void()> &onSuccess, const Controller::DatasetFailCallback &onFailure, const CommandOptions &options) override
collect status from NFD
CommandParser & addAlias(const std::string &noun, const std::string &verb, const std::string &verb2)
add an alias "noun verb2" to existing command "noun verb"
print different string on first and subsequent usage
print boolean as &#39;on&#39; or &#39;off&#39;
void printDisambiguation(std::ostream &os, DisambiguationStyle style) const
print results for disambiguation
Definition: find-face.cpp:173