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