face-uri.cpp
Go to the documentation of this file.
1 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2 /*
3  * Copyright (c) 2013-2018 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 ndn-cxx library (NDN C++ library with eXperimental eXtensions).
12  *
13  * ndn-cxx library is free software: you can redistribute it and/or modify it under the
14  * terms of the GNU Lesser General Public License as published by the Free Software
15  * Foundation, either version 3 of the License, or (at your option) any later version.
16  *
17  * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
18  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
19  * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
20  *
21  * You should have received copies of the GNU General Public License and GNU Lesser
22  * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
23  * <http://www.gnu.org/licenses/>.
24  *
25  * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
26  */
27 
28 #include "ndn-cxx/net/face-uri.hpp"
29 #include "ndn-cxx/net/dns.hpp"
31 
32 #include <boost/algorithm/string.hpp>
33 #include <boost/lexical_cast.hpp>
34 #include <boost/mpl/vector.hpp>
35 #include <boost/mpl/for_each.hpp>
36 
37 #include <regex>
38 #include <set>
39 #include <sstream>
40 
41 namespace ndn {
42 
43 BOOST_CONCEPT_ASSERT((boost::EqualityComparable<FaceUri>));
44 
46  : m_isV6(false)
47 {
48 }
49 
50 FaceUri::FaceUri(const std::string& uri)
51 {
52  if (!parse(uri)) {
53  BOOST_THROW_EXCEPTION(Error("Malformed URI: " + uri));
54  }
55 }
56 
57 FaceUri::FaceUri(const char* uri)
58  : FaceUri(std::string(uri))
59 {
60 }
61 
62 bool
63 FaceUri::parse(const std::string& uri)
64 {
65  m_scheme.clear();
66  m_host.clear();
67  m_port.clear();
68  m_path.clear();
69  m_isV6 = false;
70 
71  static const std::regex protocolExp("(\\w+\\d?(\\+\\w+)?)://([^/]*)(\\/[^?]*)?");
72  std::smatch protocolMatch;
73  if (!std::regex_match(uri, protocolMatch, protocolExp)) {
74  return false;
75  }
76  m_scheme = protocolMatch[1];
77  std::string authority = protocolMatch[3];
78  m_path = protocolMatch[4];
79 
80  // pattern for IPv6 link local address enclosed in [ ], with optional port number
81  static const std::regex v6LinkLocalExp("^\\[([a-fA-F0-9:]+)%([^\\s/:]+)\\](?:\\:(\\d+))?$");
82  // pattern for IPv6 address enclosed in [ ], with optional port number
83  static const std::regex v6Exp("^\\[([a-fA-F0-9:]+)\\](?:\\:(\\d+))?$");
84  // pattern for Ethernet address in standard hex-digits-and-colons notation
85  static const std::regex etherExp("^\\[((?:[a-fA-F0-9]{1,2}\\:){5}(?:[a-fA-F0-9]{1,2}))\\]$");
86  // pattern for IPv4-mapped IPv6 address, with optional port number
87  static const std::regex v4MappedV6Exp("^\\[::ffff:(\\d+(?:\\.\\d+){3})\\](?:\\:(\\d+))?$");
88  // pattern for IPv4/hostname/fd/ifname, with optional port number
89  static const std::regex v4HostExp("^([^:]+)(?:\\:(\\d+))?$");
90 
91  if (authority.empty()) {
92  // UNIX, internal
93  }
94  else {
95  std::smatch match;
96  if (std::regex_match(authority, match, v6LinkLocalExp)) {
97  m_isV6 = true;
98  m_host = match[1].str() + "%" + match[2].str();
99  m_port = match[3];
100  return true;
101  }
102 
103  m_isV6 = std::regex_match(authority, match, v6Exp);
104  if (m_isV6 ||
105  std::regex_match(authority, match, etherExp) ||
106  std::regex_match(authority, match, v4MappedV6Exp) ||
107  std::regex_match(authority, match, v4HostExp)) {
108  m_host = match[1];
109  m_port = match[2];
110  }
111  else {
112  return false;
113  }
114  }
115 
116  return true;
117 }
118 
119 FaceUri::FaceUri(const boost::asio::ip::udp::endpoint& endpoint)
120 {
121  m_isV6 = endpoint.address().is_v6();
122  m_scheme = m_isV6 ? "udp6" : "udp4";
123  m_host = endpoint.address().to_string();
124  m_port = to_string(endpoint.port());
125 }
126 
127 FaceUri::FaceUri(const boost::asio::ip::tcp::endpoint& endpoint)
128 {
129  m_isV6 = endpoint.address().is_v6();
130  m_scheme = m_isV6 ? "tcp6" : "tcp4";
131  m_host = endpoint.address().to_string();
132  m_port = to_string(endpoint.port());
133 }
134 
135 FaceUri::FaceUri(const boost::asio::ip::tcp::endpoint& endpoint, const std::string& scheme)
136 {
137  m_isV6 = endpoint.address().is_v6();
138  m_scheme = scheme;
139  m_host = endpoint.address().to_string();
140  m_port = to_string(endpoint.port());
141 }
142 
143 #ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
144 FaceUri::FaceUri(const boost::asio::local::stream_protocol::endpoint& endpoint)
145  : m_scheme("unix")
146  , m_path(endpoint.path())
147  , m_isV6(false)
148 {
149 }
150 #endif // BOOST_ASIO_HAS_LOCAL_SOCKETS
151 
152 FaceUri
154 {
155  FaceUri uri;
156  uri.m_scheme = "fd";
157  uri.m_host = to_string(fd);
158  return uri;
159 }
160 
162  : m_scheme("ether")
163  , m_host(address.toString())
164  , m_isV6(true)
165 {
166 }
167 
168 FaceUri
169 FaceUri::fromDev(const std::string& ifname)
170 {
171  FaceUri uri;
172  uri.m_scheme = "dev";
173  uri.m_host = ifname;
174  return uri;
175 }
176 
177 FaceUri
178 FaceUri::fromUdpDev(const boost::asio::ip::udp::endpoint& endpoint, const std::string& ifname)
179 {
180  FaceUri uri;
181  uri.m_scheme = endpoint.address().is_v6() ? "udp6+dev" : "udp4+dev";
182  uri.m_host = ifname;
183  uri.m_port = to_string(endpoint.port());
184  return uri;
185 }
186 
187 bool
188 FaceUri::operator==(const FaceUri& rhs) const
189 {
190  return m_isV6 == rhs.m_isV6 &&
191  m_scheme == rhs.m_scheme &&
192  m_host == rhs.m_host &&
193  m_port == rhs.m_port &&
194  m_path == rhs.m_path;
195 }
196 
197 bool
198 FaceUri::operator!=(const FaceUri& rhs) const
199 {
200  return !(*this == rhs);
201 }
202 
203 std::string
205 {
206  std::ostringstream os;
207  os << *this;
208  return os.str();
209 }
210 
211 std::ostream&
212 operator<<(std::ostream& os, const FaceUri& uri)
213 {
214  os << uri.m_scheme << "://";
215  if (uri.m_isV6) {
216  os << "[" << uri.m_host << "]";
217  }
218  else {
219  os << uri.m_host;
220  }
221  if (!uri.m_port.empty()) {
222  os << ":" << uri.m_port;
223  }
224  os << uri.m_path;
225  return os;
226 }
227 
228 
231 class CanonizeProvider : noncopyable
232 {
233 public:
234  virtual
235  ~CanonizeProvider() = default;
236 
237  virtual std::set<std::string>
238  getSchemes() const = 0;
239 
240  virtual bool
241  isCanonical(const FaceUri& faceUri) const = 0;
242 
243  virtual void
244  canonize(const FaceUri& faceUri,
245  const FaceUri::CanonizeSuccessCallback& onSuccess,
246  const FaceUri::CanonizeFailureCallback& onFailure,
247  boost::asio::io_service& io, time::nanoseconds timeout) const = 0;
248 };
249 
250 template<typename Protocol>
251 class IpHostCanonizeProvider : public CanonizeProvider
252 {
253 public:
254  std::set<std::string>
255  getSchemes() const override
256  {
257  return {m_baseScheme, m_v4Scheme, m_v6Scheme};
258  }
259 
260  bool
261  isCanonical(const FaceUri& faceUri) const override
262  {
263  if (faceUri.getPort().empty()) {
264  return false;
265  }
266  if (!faceUri.getPath().empty()) {
267  return false;
268  }
269 
270  boost::system::error_code ec;
271  auto addr = boost::asio::ip::address::from_string(unescapeHost(faceUri.getHost()), ec);
272  if (ec) {
273  return false;
274  }
275 
276  bool hasCorrectScheme = (faceUri.getScheme() == m_v4Scheme && addr.is_v4()) ||
277  (faceUri.getScheme() == m_v6Scheme && addr.is_v6());
278  if (!hasCorrectScheme) {
279  return false;
280  }
281 
282  auto checkAddressWithUri = [] (const boost::asio::ip::address& addr,
283  const FaceUri& faceUri) -> bool {
284  if (addr.is_v4() || !addr.to_v6().is_link_local()) {
285  return addr.to_string() == faceUri.getHost();
286  }
287 
288  std::vector<std::string> addrFields, faceUriFields;
289  std::string addrString = addr.to_string();
290  std::string faceUriString = faceUri.getHost();
291 
292  boost::algorithm::split(addrFields, addrString, boost::is_any_of("%"));
293  boost::algorithm::split(faceUriFields, faceUriString, boost::is_any_of("%"));
294  if (addrFields.size() != 2 || faceUriFields.size() != 2) {
295  return false;
296  }
297 
298  if (faceUriFields[1].size() > 2 && faceUriFields[1].compare(0, 2, "25") == 0) {
299  // %25... is accepted, but not a canonical form
300  return false;
301  }
302 
303  return addrFields[0] == faceUriFields[0] &&
304  addrFields[1] == faceUriFields[1];
305  };
306 
307  return checkAddressWithUri(addr, faceUri) && checkAddress(addr).first;
308  }
309 
310  void
311  canonize(const FaceUri& faceUri,
312  const FaceUri::CanonizeSuccessCallback& onSuccess,
313  const FaceUri::CanonizeFailureCallback& onFailure,
314  boost::asio::io_service& io, time::nanoseconds timeout) const override
315  {
316  if (this->isCanonical(faceUri)) {
317  onSuccess(faceUri);
318  return;
319  }
320 
321  // make a copy because caller may modify faceUri
322  auto uri = make_shared<FaceUri>(faceUri);
323  boost::system::error_code ec;
324  auto ipAddress = boost::asio::ip::address::from_string(unescapeHost(faceUri.getHost()), ec);
325  if (!ec) {
326  // No need to resolve IP address if host is already an IP
327  if ((faceUri.getScheme() == m_v4Scheme && !ipAddress.is_v4()) ||
328  (faceUri.getScheme() == m_v6Scheme && !ipAddress.is_v6())) {
329  return onFailure("IPv4/v6 mismatch");
330  }
331 
332  onDnsSuccess(uri, onSuccess, onFailure, ipAddress);
333  }
334  else {
335  dns::AddressSelector addressSelector;
336  if (faceUri.getScheme() == m_v4Scheme) {
337  addressSelector = dns::Ipv4Only();
338  }
339  else if (faceUri.getScheme() == m_v6Scheme) {
340  addressSelector = dns::Ipv6Only();
341  }
342  else {
343  BOOST_ASSERT(faceUri.getScheme() == m_baseScheme);
344  addressSelector = dns::AnyAddress();
345  }
346 
347  dns::asyncResolve(unescapeHost(faceUri.getHost()),
348  bind(&IpHostCanonizeProvider<Protocol>::onDnsSuccess, this, uri, onSuccess, onFailure, _1),
349  bind(&IpHostCanonizeProvider<Protocol>::onDnsFailure, this, uri, onFailure, _1),
350  io, addressSelector, timeout);
351  }
352  }
353 
354 protected:
355  explicit
356  IpHostCanonizeProvider(const std::string& baseScheme,
357  uint16_t defaultUnicastPort = 6363,
358  uint16_t defaultMulticastPort = 56363)
359  : m_baseScheme(baseScheme)
360  , m_v4Scheme(baseScheme + '4')
361  , m_v6Scheme(baseScheme + '6')
362  , m_defaultUnicastPort(defaultUnicastPort)
363  , m_defaultMulticastPort(defaultMulticastPort)
364  {
365  }
366 
367 private:
368  void
369  onDnsSuccess(const shared_ptr<FaceUri>& faceUri,
370  const FaceUri::CanonizeSuccessCallback& onSuccess,
371  const FaceUri::CanonizeFailureCallback& onFailure,
372  const dns::IpAddress& ipAddress) const
373  {
374  bool isOk = false;
375  std::string reason;
376  std::tie(isOk, reason) = this->checkAddress(ipAddress);
377  if (!isOk) {
378  return onFailure(reason);
379  }
380 
381  uint16_t port = 0;
382  if (faceUri->getPort().empty()) {
383  port = ipAddress.is_multicast() ? m_defaultMulticastPort : m_defaultUnicastPort;
384  }
385  else {
386  try {
387  port = boost::lexical_cast<uint16_t>(faceUri->getPort());
388  }
389  catch (const boost::bad_lexical_cast&) {
390  return onFailure("invalid port number '" + faceUri->getPort() + "'");
391  }
392  }
393 
394  FaceUri canonicalUri(typename Protocol::endpoint(ipAddress, port));
395  BOOST_ASSERT(canonicalUri.isCanonical());
396  onSuccess(canonicalUri);
397  }
398 
399  void
400  onDnsFailure(const shared_ptr<FaceUri>& faceUri,
401  const FaceUri::CanonizeFailureCallback& onFailure,
402  const std::string& reason) const
403  {
404  onFailure(reason);
405  }
406 
411  virtual std::pair<bool, std::string>
412  checkAddress(const dns::IpAddress& ipAddress) const
413  {
414  return {true, ""};
415  }
416 
417  static std::string
418  unescapeHost(std::string host)
419  {
420  auto escapePos = host.find("%25");
421  if (escapePos != std::string::npos && escapePos < host.size() - 3) {
422  host = unescape(host);
423  }
424  return host;
425  }
426 
427 private:
428  std::string m_baseScheme;
429  std::string m_v4Scheme;
430  std::string m_v6Scheme;
431  uint16_t m_defaultUnicastPort;
432  uint16_t m_defaultMulticastPort;
433 };
434 
435 class UdpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::udp>
436 {
437 public:
438  UdpCanonizeProvider()
439  : IpHostCanonizeProvider("udp")
440  {
441  }
442 };
443 
444 class TcpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::tcp>
445 {
446 public:
447  TcpCanonizeProvider()
448  : IpHostCanonizeProvider("tcp")
449  {
450  }
451 
452 protected:
453  std::pair<bool, std::string>
454  checkAddress(const dns::IpAddress& ipAddress) const override
455  {
456  if (ipAddress.is_multicast()) {
457  return {false, "cannot use multicast address"};
458  }
459  return {true, ""};
460  }
461 };
462 
463 class EtherCanonizeProvider : public CanonizeProvider
464 {
465 public:
466  std::set<std::string>
467  getSchemes() const override
468  {
469  return {"ether"};
470  }
471 
472  bool
473  isCanonical(const FaceUri& faceUri) const override
474  {
475  if (!faceUri.getPort().empty()) {
476  return false;
477  }
478  if (!faceUri.getPath().empty()) {
479  return false;
480  }
481 
482  auto addr = ethernet::Address::fromString(faceUri.getHost());
483  return addr.toString() == faceUri.getHost();
484  }
485 
486  void
487  canonize(const FaceUri& faceUri,
488  const FaceUri::CanonizeSuccessCallback& onSuccess,
489  const FaceUri::CanonizeFailureCallback& onFailure,
490  boost::asio::io_service& io, time::nanoseconds timeout) const override
491  {
492  auto addr = ethernet::Address::fromString(faceUri.getHost());
493  if (addr.isNull()) {
494  return onFailure("invalid ethernet address '" + faceUri.getHost() + "'");
495  }
496 
497  FaceUri canonicalUri(addr);
498  BOOST_ASSERT(canonicalUri.isCanonical());
499  onSuccess(canonicalUri);
500  }
501 };
502 
503 class DevCanonizeProvider : public CanonizeProvider
504 {
505 public:
506  std::set<std::string>
507  getSchemes() const override
508  {
509  return {"dev"};
510  }
511 
512  bool
513  isCanonical(const FaceUri& faceUri) const override
514  {
515  return !faceUri.getHost().empty() && faceUri.getPort().empty() && faceUri.getPath().empty();
516  }
517 
518  void
519  canonize(const FaceUri& faceUri,
520  const FaceUri::CanonizeSuccessCallback& onSuccess,
521  const FaceUri::CanonizeFailureCallback& onFailure,
522  boost::asio::io_service& io, time::nanoseconds timeout) const override
523  {
524  if (faceUri.getHost().empty()) {
525  onFailure("network interface name is missing");
526  return;
527  }
528  if (!faceUri.getPort().empty()) {
529  onFailure("port number is not allowed");
530  return;
531  }
532  if (!faceUri.getPath().empty() && faceUri.getPath() != "/") { // permit trailing slash only
533  onFailure("path is not allowed");
534  return;
535  }
536 
537  FaceUri canonicalUri = FaceUri::fromDev(faceUri.getHost());
538  BOOST_ASSERT(canonicalUri.isCanonical());
539  onSuccess(canonicalUri);
540  }
541 };
542 
543 class UdpDevCanonizeProvider : public CanonizeProvider
544 {
545 public:
546  std::set<std::string>
547  getSchemes() const override
548  {
549  return {"udp4+dev", "udp6+dev"};
550  }
551 
552  bool
553  isCanonical(const FaceUri& faceUri) const override
554  {
555  if (faceUri.getPort().empty()) {
556  return false;
557  }
558  if (!faceUri.getPath().empty()) {
559  return false;
560  }
561  return true;
562  }
563 
564  void
565  canonize(const FaceUri& faceUri,
566  const FaceUri::CanonizeSuccessCallback& onSuccess,
567  const FaceUri::CanonizeFailureCallback& onFailure,
568  boost::asio::io_service& io, time::nanoseconds timeout) const override
569  {
570  if (this->isCanonical(faceUri)) {
571  onSuccess(faceUri);
572  }
573  else {
574  onFailure("cannot canonize " + faceUri.toString());
575  }
576  }
577 };
578 
579 using CanonizeProviders = boost::mpl::vector<UdpCanonizeProvider*,
580  TcpCanonizeProvider*,
581  EtherCanonizeProvider*,
582  DevCanonizeProvider*,
583  UdpDevCanonizeProvider*>;
584 using CanonizeProviderTable = std::map<std::string, shared_ptr<CanonizeProvider>>;
585 
586 class CanonizeProviderTableInitializer
587 {
588 public:
589  explicit
590  CanonizeProviderTableInitializer(CanonizeProviderTable& providerTable)
591  : m_providerTable(providerTable)
592  {
593  }
594 
595  template<typename CP>
596  void
597  operator()(CP*)
598  {
599  shared_ptr<CanonizeProvider> cp = make_shared<CP>();
600  auto schemes = cp->getSchemes();
601  BOOST_ASSERT(!schemes.empty());
602 
603  for (const auto& scheme : schemes) {
604  BOOST_ASSERT(m_providerTable.count(scheme) == 0);
605  m_providerTable[scheme] = cp;
606  }
607  }
608 
609 private:
610  CanonizeProviderTable& m_providerTable;
611 };
612 
613 static const CanonizeProvider*
614 getCanonizeProvider(const std::string& scheme)
615 {
616  static CanonizeProviderTable providerTable;
617  if (providerTable.empty()) {
618  boost::mpl::for_each<CanonizeProviders>(CanonizeProviderTableInitializer(providerTable));
619  BOOST_ASSERT(!providerTable.empty());
620  }
621 
622  auto it = providerTable.find(scheme);
623  return it == providerTable.end() ? nullptr : it->second.get();
624 }
625 
626 
627 bool
628 FaceUri::canCanonize(const std::string& scheme)
629 {
630  return getCanonizeProvider(scheme) != nullptr;
631 }
632 
633 bool
635 {
636  const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
637  if (cp == nullptr) {
638  return false;
639  }
640 
641  return cp->isCanonical(*this);
642 }
643 
644 void
646  const CanonizeFailureCallback& onFailure,
647  boost::asio::io_service& io, time::nanoseconds timeout) const
648 {
649  const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
650  if (cp == nullptr) {
651  if (onFailure) {
652  onFailure("scheme not supported");
653  }
654  return;
655  }
656 
657  cp->canonize(*this,
658  onSuccess ? onSuccess : [] (auto&&) {},
659  onFailure ? onFailure : [] (auto&&) {},
660  io, timeout);
661 }
662 
663 } // namespace ndn
static Address fromString(const std::string &str)
Creates an Address from a string containing an Ethernet address in hexadecimal notation, with colons or hyphens as separators.
Definition: ethernet.cpp:92
Definition: data.cpp:26
function< void(const std::string &reason)> CanonizeFailureCallback
Definition: face-uri.hpp:165
static FaceUri fromFd(int fd)
create fd FaceUri from file descriptor
Definition: face-uri.cpp:153
function< void(const FaceUri &)> CanonizeSuccessCallback
Definition: face-uri.hpp:164
static bool canCanonize(const std::string &scheme)
Definition: face-uri.cpp:628
static const CanonizeProvider * getCanonizeProvider(const std::string &scheme)
Definition: face-uri.cpp:614
STL namespace.
function< bool(const IpAddress &address)> AddressSelector
Definition: dns.hpp:34
static FaceUri fromUdpDev(const boost::asio::ip::udp::endpoint &endpoint, const std::string &ifname)
create udp4 or udp6 NIC-associated FaceUri from endpoint and network device name
Definition: face-uri.cpp:178
bool operator!=(const FaceUri &rhs) const
Definition: face-uri.cpp:198
void canonize(const CanonizeSuccessCallback &onSuccess, const CanonizeFailureCallback &onFailure, boost::asio::io_service &io, time::nanoseconds timeout) const
asynchronously convert this FaceUri to canonical form
Definition: face-uri.cpp:645
bool parse(const std::string &uri)
exception-safe parsing
Definition: face-uri.cpp:63
std::string toString() const
write as a string
Definition: face-uri.cpp:204
std::string unescape(const std::string &str)
Decode a percent-encoded string.
std::string toString(char sep= ':') const
Converts the address to a human-readable string.
Definition: ethernet.cpp:78
represents the underlying protocol and address used by a Face
Definition: face-uri.hpp:44
std::map< std::string, shared_ptr< CanonizeProvider >> CanonizeProviderTable
Definition: face-uri.cpp:584
represents an Ethernet hardware address
Definition: ethernet.hpp:52
bool isCanonical() const
determine whether this FaceUri is in canonical form
Definition: face-uri.cpp:634
bool operator==(const FaceUri &rhs) const
Definition: face-uri.cpp:188
const std::string & getScheme() const
get scheme (protocol)
Definition: face-uri.hpp:113
boost::asio::ip::address IpAddress
Definition: dns.hpp:33
std::string to_string(const V &v)
Definition: backports.hpp:67
const std::string & getHost() const
get host (domain)
Definition: face-uri.hpp:120
const std::string & getPort() const
get port
Definition: face-uri.hpp:127
boost::mpl::vector< UdpCanonizeProvider *, TcpCanonizeProvider *, EtherCanonizeProvider *, DevCanonizeProvider *, UdpDevCanonizeProvider * > CanonizeProviders
Definition: face-uri.cpp:583
void asyncResolve(const std::string &host, const SuccessCallback &onSuccess, const ErrorCallback &onError, boost::asio::io_service &ioService, const AddressSelector &addressSelector, time::nanoseconds timeout)
Asynchronously resolve host.
Definition: dns.cpp:132
static FaceUri fromDev(const std::string &ifname)
create dev FaceUri from network device name
Definition: face-uri.cpp:169
const std::string & getPath() const
get path
Definition: face-uri.hpp:134
friend std::ostream & operator<<(std::ostream &os, const FaceUri &uri)
Definition: face-uri.cpp:212