32 #include <boost/algorithm/string/classification.hpp>
33 #include <boost/algorithm/string/split.hpp>
34 #include <boost/lexical_cast.hpp>
35 #include <boost/mpl/for_each.hpp>
36 #include <boost/mpl/vector.hpp>
44 BOOST_CONCEPT_ASSERT((boost::EqualityComparable<FaceUri>));
72 static const std::regex protocolExp(
"(\\w+\\d?(\\+\\w+)?)://([^/]*)(\\/[^?]*)?");
73 std::smatch protocolMatch;
74 if (!std::regex_match(uri, protocolMatch, protocolExp)) {
77 m_scheme = protocolMatch[1];
78 std::string authority = protocolMatch[3];
79 m_path = protocolMatch[4];
82 static const std::regex v6LinkLocalExp(
"^\\[([a-fA-F0-9:]+)%([^\\s/:]+)\\](?:\\:(\\d+))?$");
84 static const std::regex v6Exp(
"^\\[([a-fA-F0-9:]+)\\](?:\\:(\\d+))?$");
86 static const std::regex etherExp(
"^\\[((?:[a-fA-F0-9]{1,2}\\:){5}(?:[a-fA-F0-9]{1,2}))\\]$");
88 static const std::regex v4MappedV6Exp(
"^\\[::ffff:(\\d+(?:\\.\\d+){3})\\](?:\\:(\\d+))?$");
90 static const std::regex v4HostExp(
"^([^:]+)(?:\\:(\\d+))?$");
92 if (authority.empty()) {
97 if (std::regex_match(authority, match, v6LinkLocalExp)) {
99 m_host = match[1].str() +
"%" + match[2].str();
104 m_isV6 = std::regex_match(authority, match, v6Exp);
106 std::regex_match(authority, match, etherExp) ||
107 std::regex_match(authority, match, v4MappedV6Exp) ||
108 std::regex_match(authority, match, v4HostExp)) {
122 m_isV6 = endpoint.address().is_v6();
123 m_scheme = m_isV6 ?
"udp6" :
"udp4";
124 m_host = endpoint.address().to_string();
130 m_isV6 = endpoint.address().is_v6();
131 m_scheme = m_isV6 ?
"tcp6" :
"tcp4";
132 m_host = endpoint.address().to_string();
136 FaceUri::FaceUri(
const boost::asio::ip::tcp::endpoint& endpoint,
const std::string& scheme)
138 m_isV6 = endpoint.address().is_v6();
140 m_host = endpoint.address().to_string();
144 #ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
145 FaceUri::FaceUri(
const boost::asio::local::stream_protocol::endpoint& endpoint)
147 , m_path(endpoint.path())
173 uri.m_scheme =
"dev";
182 uri.m_scheme = endpoint.address().is_v6() ?
"udp6+dev" :
"udp4+dev";
191 std::ostringstream os;
199 os << uri.m_scheme <<
"://";
201 os <<
"[" << uri.m_host <<
"]";
206 if (!uri.m_port.empty()) {
207 os <<
":" << uri.m_port;
216 class CanonizeProvider : noncopyable
220 ~CanonizeProvider() =
default;
222 virtual std::set<std::string>
223 getSchemes()
const = 0;
226 isCanonical(
const FaceUri& faceUri)
const = 0;
229 canonize(
const FaceUri& faceUri,
235 template<
typename Protocol>
236 class IpHostCanonizeProvider :
public CanonizeProvider
239 std::set<std::string>
240 getSchemes()
const override
242 return {m_baseScheme, m_v4Scheme, m_v6Scheme};
246 isCanonical(
const FaceUri& faceUri)
const override
248 if (faceUri.getPort().empty()) {
251 if (!faceUri.getPath().empty()) {
255 boost::system::error_code ec;
256 auto addr = boost::asio::ip::address::from_string(unescapeHost(faceUri.getHost()), ec);
261 bool hasCorrectScheme = (faceUri.getScheme() == m_v4Scheme && addr.is_v4()) ||
262 (faceUri.getScheme() == m_v6Scheme && addr.is_v6());
263 if (!hasCorrectScheme) {
267 auto checkAddressWithUri = [] (
const boost::asio::ip::address& addr,
268 const FaceUri& faceUri) ->
bool {
269 if (addr.is_v4() || !addr.to_v6().is_link_local()) {
270 return addr.to_string() == faceUri.getHost();
273 std::vector<std::string> addrFields, faceUriFields;
274 std::string addrString = addr.to_string();
275 std::string faceUriString = faceUri.getHost();
277 boost::algorithm::split(addrFields, addrString, boost::is_any_of(
"%"));
278 boost::algorithm::split(faceUriFields, faceUriString, boost::is_any_of(
"%"));
279 if (addrFields.size() != 2 || faceUriFields.size() != 2) {
283 if (faceUriFields[1].size() > 2 && faceUriFields[1].compare(0, 2,
"25") == 0) {
288 return addrFields[0] == faceUriFields[0] &&
289 addrFields[1] == faceUriFields[1];
292 return checkAddressWithUri(addr, faceUri) && checkAddress(addr).first;
296 canonize(
const FaceUri& faceUri,
301 if (this->isCanonical(faceUri)) {
307 auto uri = make_shared<FaceUri>(faceUri);
308 boost::system::error_code ec;
309 auto ipAddress = boost::asio::ip::address::from_string(unescapeHost(faceUri.getHost()), ec);
312 if ((faceUri.getScheme() == m_v4Scheme && !ipAddress.is_v4()) ||
313 (faceUri.getScheme() == m_v6Scheme && !ipAddress.is_v6())) {
314 return onFailure(
"IPv4/v6 mismatch");
317 onDnsSuccess(uri, onSuccess, onFailure, ipAddress);
321 if (faceUri.getScheme() == m_v4Scheme) {
322 addressSelector = dns::Ipv4Only();
324 else if (faceUri.getScheme() == m_v6Scheme) {
325 addressSelector = dns::Ipv6Only();
328 BOOST_ASSERT(faceUri.getScheme() == m_baseScheme);
329 addressSelector = dns::AnyAddress();
333 [=] (
const auto& ipAddr) { onDnsSuccess(uri, onSuccess, onFailure, ipAddr); },
334 [=] (
const auto& reason) { onDnsFailure(uri, onFailure, reason); },
335 io, addressSelector, timeout);
341 IpHostCanonizeProvider(
const std::string& baseScheme,
342 uint16_t defaultUnicastPort = 6363,
343 uint16_t defaultMulticastPort = 56363)
344 : m_baseScheme(baseScheme)
345 , m_v4Scheme(baseScheme +
'4')
346 , m_v6Scheme(baseScheme +
'6')
347 , m_defaultUnicastPort(defaultUnicastPort)
348 , m_defaultMulticastPort(defaultMulticastPort)
354 onDnsSuccess(
const shared_ptr<FaceUri>& faceUri,
361 std::tie(isOk, reason) = this->checkAddress(ipAddress);
363 return onFailure(reason);
367 if (faceUri->getPort().empty()) {
368 port = ipAddress.is_multicast() ? m_defaultMulticastPort : m_defaultUnicastPort;
372 port = boost::lexical_cast<uint16_t>(faceUri->getPort());
374 catch (
const boost::bad_lexical_cast&) {
375 return onFailure(
"invalid port number '" + faceUri->getPort() +
"'");
379 FaceUri canonicalUri(
typename Protocol::endpoint(ipAddress, port));
380 BOOST_ASSERT(canonicalUri.isCanonical());
381 onSuccess(canonicalUri);
385 onDnsFailure(
const shared_ptr<FaceUri>&,
387 const std::string& reason)
const
396 virtual std::pair<bool, std::string>
403 unescapeHost(std::string host)
405 auto escapePos = host.find(
"%25");
406 if (escapePos != std::string::npos && escapePos < host.size() - 3) {
413 std::string m_baseScheme;
414 std::string m_v4Scheme;
415 std::string m_v6Scheme;
416 uint16_t m_defaultUnicastPort;
417 uint16_t m_defaultMulticastPort;
420 class UdpCanonizeProvider :
public IpHostCanonizeProvider<boost::asio::ip::udp>
423 UdpCanonizeProvider()
424 : IpHostCanonizeProvider(
"udp")
429 class TcpCanonizeProvider :
public IpHostCanonizeProvider<boost::asio::ip::tcp>
432 TcpCanonizeProvider()
433 : IpHostCanonizeProvider(
"tcp")
438 std::pair<bool, std::string>
441 if (ipAddress.is_multicast()) {
442 return {
false,
"cannot use multicast address"};
448 class EtherCanonizeProvider :
public CanonizeProvider
451 std::set<std::string>
452 getSchemes()
const override
458 isCanonical(
const FaceUri& faceUri)
const override
460 if (!faceUri.getPort().empty()) {
463 if (!faceUri.getPath().empty()) {
468 return addr.toString() == faceUri.getHost();
472 canonize(
const FaceUri& faceUri,
479 return onFailure(
"invalid ethernet address '" + faceUri.getHost() +
"'");
482 FaceUri canonicalUri(addr);
483 BOOST_ASSERT(canonicalUri.isCanonical());
484 onSuccess(canonicalUri);
488 class DevCanonizeProvider :
public CanonizeProvider
491 std::set<std::string>
492 getSchemes()
const override
498 isCanonical(
const FaceUri& faceUri)
const override
500 return !faceUri.getHost().empty() && faceUri.getPort().empty() && faceUri.getPath().empty();
504 canonize(
const FaceUri& faceUri,
509 if (faceUri.getHost().empty()) {
510 onFailure(
"network interface name is missing");
513 if (!faceUri.getPort().empty()) {
514 onFailure(
"port number is not allowed");
517 if (!faceUri.getPath().empty() && faceUri.getPath() !=
"/") {
518 onFailure(
"path is not allowed");
523 BOOST_ASSERT(canonicalUri.isCanonical());
524 onSuccess(canonicalUri);
528 class UdpDevCanonizeProvider :
public CanonizeProvider
531 std::set<std::string>
532 getSchemes()
const override
534 return {
"udp4+dev",
"udp6+dev"};
538 isCanonical(
const FaceUri& faceUri)
const override
540 if (faceUri.getPort().empty()) {
543 if (!faceUri.getPath().empty()) {
550 canonize(
const FaceUri& faceUri,
555 if (this->isCanonical(faceUri)) {
559 onFailure(
"cannot canonize " + faceUri.toString());
565 TcpCanonizeProvider*,
566 EtherCanonizeProvider*,
567 DevCanonizeProvider*,
568 UdpDevCanonizeProvider*>;
571 class CanonizeProviderTableInitializer
576 : m_providerTable(providerTable)
580 template<
typename CP>
584 shared_ptr<CanonizeProvider> cp = make_shared<CP>();
585 auto schemes = cp->getSchemes();
586 BOOST_ASSERT(!schemes.empty());
588 for (
const auto& scheme : schemes) {
589 BOOST_ASSERT(m_providerTable.count(scheme) == 0);
590 m_providerTable[scheme] = cp;
598 static const CanonizeProvider*
599 getCanonizeProvider(
const std::string& scheme)
602 if (providerTable.empty()) {
603 boost::mpl::for_each<CanonizeProviders>(CanonizeProviderTableInitializer(providerTable));
604 BOOST_ASSERT(!providerTable.empty());
607 auto it = providerTable.find(scheme);
608 return it == providerTable.end() ? nullptr : it->second.get();
615 return getCanonizeProvider(scheme) !=
nullptr;
621 const CanonizeProvider* cp = getCanonizeProvider(this->
getScheme());
626 return cp->isCanonical(*
this);
634 const CanonizeProvider* cp = getCanonizeProvider(this->
getScheme());
637 onFailure(
"scheme not supported");
643 onSuccess ? onSuccess : [] (
auto&&) {},
644 onFailure ? onFailure : [] (
auto&&) {},
The underlying protocol and address used by a Face.
std::string toString() const
Serialize as a string.
function< void(const FaceUri &)> CanonizeSuccessCallback
const std::string & getScheme() const
Get scheme (protocol)
bool parse(const std::string &uri)
Exception-safe parsing.
static bool canCanonize(const std::string &scheme)
Return whether a FaceUri of the specified scheme can be canonized.
bool isCanonical() const
Determine whether this FaceUri is in canonical form.
static FaceUri fromDev(const std::string &ifname)
Construct a dev FaceUri from a network device name.
static FaceUri fromUdpDev(const boost::asio::ip::udp::endpoint &endpoint, const std::string &ifname)
Construct a udp4 or udp6 NIC-associated FaceUri from endpoint and network device name.
void canonize(const CanonizeSuccessCallback &onSuccess, const CanonizeFailureCallback &onFailure, boost::asio::io_service &io, time::nanoseconds timeout) const
Asynchronously convert this FaceUri to canonical form.
function< void(const std::string &reason)> CanonizeFailureCallback
static FaceUri fromFd(int fd)
Construct an fd FaceUri from a file descriptor.
Represents an Ethernet hardware address.
static Address fromString(const std::string &str)
Creates an Address from a string containing an Ethernet address in hexadecimal notation,...
boost::asio::ip::address IpAddress
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.
function< bool(const IpAddress &address)> AddressSelector
std::string to_string(const errinfo_stacktrace &x)
std::string toString(const system_clock::time_point &timePoint, const std::string &format, const std::locale &locale)
Convert time point to string with specified format.
boost::chrono::nanoseconds nanoseconds
std::string unescape(const std::string &str)
Decode a percent-encoded string.
std::map< std::string, shared_ptr< CanonizeProvider > > CanonizeProviderTable
std::ostream & operator<<(std::ostream &os, const Data &data)
boost::mpl::vector< UdpCanonizeProvider *, TcpCanonizeProvider *, EtherCanonizeProvider *, DevCanonizeProvider *, UdpDevCanonizeProvider * > CanonizeProviders