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> 43 BOOST_CONCEPT_ASSERT((boost::EqualityComparable<FaceUri>));
71 static const std::regex protocolExp(
"(\\w+\\d?(\\+\\w+)?)://([^/]*)(\\/[^?]*)?");
72 std::smatch protocolMatch;
73 if (!std::regex_match(uri, protocolMatch, protocolExp)) {
76 m_scheme = protocolMatch[1];
77 std::string authority = protocolMatch[3];
78 m_path = protocolMatch[4];
81 static const std::regex v6LinkLocalExp(
"^\\[([a-fA-F0-9:]+)%([^\\s/:]+)\\](?:\\:(\\d+))?$");
83 static const std::regex v6Exp(
"^\\[([a-fA-F0-9:]+)\\](?:\\:(\\d+))?$");
85 static const std::regex etherExp(
"^\\[((?:[a-fA-F0-9]{1,2}\\:){5}(?:[a-fA-F0-9]{1,2}))\\]$");
87 static const std::regex v4MappedV6Exp(
"^\\[::ffff:(\\d+(?:\\.\\d+){3})\\](?:\\:(\\d+))?$");
89 static const std::regex v4HostExp(
"^([^:]+)(?:\\:(\\d+))?$");
91 if (authority.empty()) {
96 if (std::regex_match(authority, match, v6LinkLocalExp)) {
98 m_host = match[1].str() +
"%" + match[2].str();
103 m_isV6 = std::regex_match(authority, match, v6Exp);
105 std::regex_match(authority, match, etherExp) ||
106 std::regex_match(authority, match, v4MappedV6Exp) ||
107 std::regex_match(authority, match, v4HostExp)) {
121 m_isV6 = endpoint.address().is_v6();
122 m_scheme = m_isV6 ?
"udp6" :
"udp4";
123 m_host = endpoint.address().to_string();
129 m_isV6 = endpoint.address().is_v6();
130 m_scheme = m_isV6 ?
"tcp6" :
"tcp4";
131 m_host = endpoint.address().to_string();
135 FaceUri::FaceUri(
const boost::asio::ip::tcp::endpoint& endpoint,
const std::string& scheme)
137 m_isV6 = endpoint.address().is_v6();
139 m_host = endpoint.address().to_string();
143 #ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS 144 FaceUri::FaceUri(
const boost::asio::local::stream_protocol::endpoint& endpoint)
146 , m_path(endpoint.path())
150 #endif // BOOST_ASIO_HAS_LOCAL_SOCKETS 172 uri.m_scheme =
"dev";
181 uri.m_scheme = endpoint.address().is_v6() ?
"udp6+dev" :
"udp4+dev";
190 std::ostringstream os;
198 os << uri.m_scheme <<
"://";
200 os <<
"[" << uri.m_host <<
"]";
205 if (!uri.m_port.empty()) {
206 os <<
":" << uri.m_port;
215 class CanonizeProvider : noncopyable
219 ~CanonizeProvider() =
default;
221 virtual std::set<std::string>
222 getSchemes()
const = 0;
231 boost::asio::io_service& io, time::nanoseconds timeout)
const = 0;
234 template<
typename Protocol>
235 class IpHostCanonizeProvider :
public CanonizeProvider
238 std::set<std::string>
239 getSchemes()
const override 241 return {m_baseScheme, m_v4Scheme, m_v6Scheme};
247 if (faceUri.
getPort().empty()) {
250 if (!faceUri.
getPath().empty()) {
254 boost::system::error_code ec;
255 auto addr = boost::asio::ip::address::from_string(unescapeHost(faceUri.
getHost()), ec);
260 bool hasCorrectScheme = (faceUri.
getScheme() == m_v4Scheme && addr.is_v4()) ||
261 (faceUri.
getScheme() == m_v6Scheme && addr.is_v6());
262 if (!hasCorrectScheme) {
266 auto checkAddressWithUri = [] (
const boost::asio::ip::address& addr,
267 const FaceUri& faceUri) ->
bool {
268 if (addr.is_v4() || !addr.to_v6().is_link_local()) {
269 return addr.to_string() == faceUri.
getHost();
272 std::vector<std::string> addrFields, faceUriFields;
273 std::string addrString = addr.to_string();
274 std::string faceUriString = faceUri.
getHost();
276 boost::algorithm::split(addrFields, addrString, boost::is_any_of(
"%"));
277 boost::algorithm::split(faceUriFields, faceUriString, boost::is_any_of(
"%"));
278 if (addrFields.size() != 2 || faceUriFields.size() != 2) {
282 if (faceUriFields[1].size() > 2 && faceUriFields[1].compare(0, 2,
"25") == 0) {
287 return addrFields[0] == faceUriFields[0] &&
288 addrFields[1] == faceUriFields[1];
291 return checkAddressWithUri(addr, faceUri) && checkAddress(addr).first;
298 boost::asio::io_service& io, time::nanoseconds timeout)
const override 306 auto uri = make_shared<FaceUri>(faceUri);
307 boost::system::error_code ec;
308 auto ipAddress = boost::asio::ip::address::from_string(unescapeHost(faceUri.
getHost()), ec);
311 if ((faceUri.
getScheme() == m_v4Scheme && !ipAddress.is_v4()) ||
312 (faceUri.
getScheme() == m_v6Scheme && !ipAddress.is_v6())) {
313 return onFailure(
"IPv4/v6 mismatch");
316 onDnsSuccess(uri, onSuccess, onFailure, ipAddress);
323 else if (faceUri.
getScheme() == m_v6Scheme) {
327 BOOST_ASSERT(faceUri.
getScheme() == m_baseScheme);
332 bind(&IpHostCanonizeProvider<Protocol>::onDnsSuccess,
this, uri, onSuccess, onFailure, _1),
333 bind(&IpHostCanonizeProvider<Protocol>::onDnsFailure,
this, uri, onFailure, _1),
334 io, addressSelector, timeout);
340 IpHostCanonizeProvider(
const std::string& baseScheme,
341 uint16_t defaultUnicastPort = 6363,
342 uint16_t defaultMulticastPort = 56363)
343 : m_baseScheme(baseScheme)
344 , m_v4Scheme(baseScheme +
'4')
345 , m_v6Scheme(baseScheme +
'6')
346 , m_defaultUnicastPort(defaultUnicastPort)
347 , m_defaultMulticastPort(defaultMulticastPort)
353 onDnsSuccess(
const shared_ptr<FaceUri>& faceUri,
360 std::tie(isOk, reason) = this->checkAddress(ipAddress);
362 return onFailure(reason);
366 if (faceUri->getPort().empty()) {
367 port = ipAddress.is_multicast() ? m_defaultMulticastPort : m_defaultUnicastPort;
371 port = boost::lexical_cast<uint16_t>(faceUri->getPort());
373 catch (
const boost::bad_lexical_cast&) {
374 return onFailure(
"invalid port number '" + faceUri->getPort() +
"'");
378 FaceUri canonicalUri(
typename Protocol::endpoint(ipAddress, port));
379 BOOST_ASSERT(canonicalUri.isCanonical());
380 onSuccess(canonicalUri);
384 onDnsFailure(
const shared_ptr<FaceUri>& faceUri,
386 const std::string& reason)
const 395 virtual std::pair<bool, std::string>
402 unescapeHost(std::string host)
404 auto escapePos = host.find(
"%25");
405 if (escapePos != std::string::npos && escapePos < host.size() - 3) {
412 std::string m_baseScheme;
413 std::string m_v4Scheme;
414 std::string m_v6Scheme;
415 uint16_t m_defaultUnicastPort;
416 uint16_t m_defaultMulticastPort;
419 class UdpCanonizeProvider :
public IpHostCanonizeProvider<boost::asio::ip::udp>
422 UdpCanonizeProvider()
423 : IpHostCanonizeProvider(
"udp")
428 class TcpCanonizeProvider :
public IpHostCanonizeProvider<boost::asio::ip::tcp>
431 TcpCanonizeProvider()
432 : IpHostCanonizeProvider(
"tcp")
437 std::pair<bool, std::string>
440 if (ipAddress.is_multicast()) {
441 return {
false,
"cannot use multicast address"};
447 class EtherCanonizeProvider :
public CanonizeProvider
450 std::set<std::string>
451 getSchemes()
const override 459 if (!faceUri.
getPort().empty()) {
462 if (!faceUri.
getPath().empty()) {
474 boost::asio::io_service& io, time::nanoseconds timeout)
const override 478 return onFailure(
"invalid ethernet address '" + faceUri.
getHost() +
"'");
482 BOOST_ASSERT(canonicalUri.isCanonical());
483 onSuccess(canonicalUri);
487 class DevCanonizeProvider :
public CanonizeProvider
490 std::set<std::string>
491 getSchemes()
const override 506 boost::asio::io_service& io, time::nanoseconds timeout)
const override 508 if (faceUri.
getHost().empty()) {
509 onFailure(
"network interface name is missing");
512 if (!faceUri.
getPort().empty()) {
513 onFailure(
"port number is not allowed");
517 onFailure(
"path is not allowed");
523 onSuccess(canonicalUri);
527 class UdpDevCanonizeProvider :
public CanonizeProvider
530 std::set<std::string>
531 getSchemes()
const override 533 return {
"udp4+dev",
"udp6+dev"};
539 if (faceUri.
getPort().empty()) {
542 if (!faceUri.
getPath().empty()) {
552 boost::asio::io_service& io, time::nanoseconds timeout)
const override 558 onFailure(
"cannot canonize " + faceUri.
toString());
564 TcpCanonizeProvider*,
565 EtherCanonizeProvider*,
566 DevCanonizeProvider*,
567 UdpDevCanonizeProvider*>;
570 class CanonizeProviderTableInitializer
575 : m_providerTable(providerTable)
579 template<
typename CP>
583 shared_ptr<CanonizeProvider> cp = make_shared<CP>();
584 auto schemes = cp->getSchemes();
585 BOOST_ASSERT(!schemes.empty());
587 for (
const auto& scheme : schemes) {
588 BOOST_ASSERT(m_providerTable.count(scheme) == 0);
589 m_providerTable[scheme] = cp;
597 static const CanonizeProvider*
601 if (providerTable.empty()) {
602 boost::mpl::for_each<CanonizeProviders>(CanonizeProviderTableInitializer(providerTable));
603 BOOST_ASSERT(!providerTable.empty());
606 auto it = providerTable.find(scheme);
607 return it == providerTable.end() ? nullptr : it->second.get();
625 return cp->isCanonical(*
this);
631 boost::asio::io_service& io, time::nanoseconds timeout)
const 636 onFailure(
"scheme not supported");
642 onSuccess ? onSuccess : [] (
auto&&) {},
643 onFailure ? onFailure : [] (
auto&&) {},
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.
function< void(const std::string &reason)> CanonizeFailureCallback
static FaceUri fromFd(int fd)
create fd FaceUri from file descriptor
const std::string & getHost() const
get host (domain)
std::string to_string(const T &val)
function< void(const FaceUri &)> CanonizeSuccessCallback
static bool canCanonize(const std::string &scheme)
std::string toString() const
write as a string
static const CanonizeProvider * getCanonizeProvider(const std::string &scheme)
const std::string & getPort() const
get port
std::string toString(char sep=':') const
Converts the address to a human-readable string.
function< bool(const IpAddress &address)> AddressSelector
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
bool parse(const std::string &uri)
exception-safe parsing
std::string unescape(const std::string &str)
Decode a percent-encoded string.
void canonize(const CanonizeSuccessCallback &onSuccess, const CanonizeFailureCallback &onFailure, boost::asio::io_service &io, time::nanoseconds timeout) const
asynchronously convert this FaceUri to canonical form
represents the underlying protocol and address used by a Face
represents an Ethernet hardware address
const std::string & getScheme() const
get scheme (protocol)
boost::asio::ip::address IpAddress
bool isCanonical() const
determine whether this FaceUri is in canonical form
std::map< std::string, shared_ptr< CanonizeProvider > > CanonizeProviderTable
const std::string & getPath() const
get path
boost::mpl::vector< UdpCanonizeProvider *, TcpCanonizeProvider *, EtherCanonizeProvider *, DevCanonizeProvider *, UdpDevCanonizeProvider * > CanonizeProviders
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.
static FaceUri fromDev(const std::string &ifname)
create dev FaceUri from network device name
friend std::ostream & operator<<(std::ostream &os, const FaceUri &uri)