34 #include <boost/algorithm/string/classification.hpp>
35 #include <boost/algorithm/string/split.hpp>
36 #include <boost/lexical_cast.hpp>
37 #include <boost/mp11/algorithm.hpp>
38 #include <boost/mp11/list.hpp>
39 #include <boost/mp11/set.hpp>
68 static const std::regex protocolExp(
"(\\w+\\d?(\\+\\w+)?)://([^/]*)(\\/[^?]*)?");
69 std::match_results<std::string_view::const_iterator> protocolMatch;
70 if (!std::regex_match(uri.begin(), uri.end(), protocolMatch, protocolExp)) {
73 m_scheme = protocolMatch[1];
74 std::string authority = protocolMatch[3];
75 m_path = protocolMatch[4];
78 static const std::regex v6LinkLocalExp(
"^\\[([a-fA-F0-9:]+)%([^\\s/:]+)\\](?:\\:(\\d+))?$");
80 static const std::regex v6Exp(
"^\\[([a-fA-F0-9:]+)\\](?:\\:(\\d+))?$");
82 static const std::regex etherExp(
"^\\[((?:[a-fA-F0-9]{1,2}\\:){5}(?:[a-fA-F0-9]{1,2}))\\]$");
84 static const std::regex v4MappedV6Exp(
"^\\[::ffff:(\\d+(?:\\.\\d+){3})\\](?:\\:(\\d+))?$");
86 static const std::regex v4HostExp(
"^([^:]+)(?:\\:(\\d+))?$");
88 if (authority.empty()) {
93 if (std::regex_match(authority, match, v6LinkLocalExp)) {
95 m_host = match[1].str() +
"%" + match[2].str();
100 m_isV6 = std::regex_match(authority, match, v6Exp);
102 std::regex_match(authority, match, etherExp) ||
103 std::regex_match(authority, match, v4MappedV6Exp) ||
104 std::regex_match(authority, match, v4HostExp)) {
118 m_isV6 = endpoint.address().is_v6();
119 m_scheme = m_isV6 ?
"udp6" :
"udp4";
120 m_host = endpoint.address().to_string();
126 m_isV6 = endpoint.address().is_v6();
127 m_scheme = m_isV6 ?
"tcp6" :
"tcp4";
128 m_host = endpoint.address().to_string();
134 m_isV6 = endpoint.address().is_v6();
136 m_host = endpoint.address().to_string();
140 #ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
141 FaceUri::FaceUri(
const boost::asio::local::stream_protocol::endpoint& endpoint)
143 , m_path(endpoint.path())
168 uri.m_scheme =
"dev";
177 uri.m_scheme = endpoint.address().is_v6() ?
"udp6+dev" :
"udp4+dev";
186 std::ostringstream os;
192 FaceUri::print(std::ostream& os)
const
194 os << m_scheme <<
"://";
196 os <<
"[" << m_host <<
"]";
201 if (!m_port.empty()) {
211 class CanonizeProvider : noncopyable
215 ~CanonizeProvider() =
default;
217 virtual std::set<std::string>
218 getSchemes()
const = 0;
221 isCanonical(
const FaceUri& faceUri)
const = 0;
224 canonize(
const FaceUri& faceUri,
230 template<
typename Protocol>
231 class IpHostCanonizeProvider :
public CanonizeProvider
234 std::set<std::string>
235 getSchemes()
const override
237 return {m_baseScheme, m_v4Scheme, m_v6Scheme};
241 isCanonical(
const FaceUri& faceUri)
const override
243 if (faceUri.getPort().empty()) {
246 if (!faceUri.getPath().empty()) {
250 boost::system::error_code ec;
251 auto addr = boost::asio::ip::make_address(unescapeHost(faceUri.getHost()), ec);
256 bool hasCorrectScheme = (faceUri.getScheme() == m_v4Scheme && addr.is_v4()) ||
257 (faceUri.getScheme() == m_v6Scheme && addr.is_v6());
258 if (!hasCorrectScheme) {
262 auto checkAddressWithUri = [] (
const boost::asio::ip::address& addr,
263 const FaceUri& faceUri) ->
bool {
264 if (addr.is_v4() || !addr.to_v6().is_link_local()) {
265 return addr.to_string() == faceUri.getHost();
268 std::vector<std::string> addrFields, faceUriFields;
269 std::string addrString = addr.to_string();
270 std::string faceUriString = faceUri.getHost();
272 boost::algorithm::split(addrFields, addrString, boost::is_any_of(
"%"));
273 boost::algorithm::split(faceUriFields, faceUriString, boost::is_any_of(
"%"));
274 if (addrFields.size() != 2 || faceUriFields.size() != 2) {
278 if (faceUriFields[1].size() > 2 && faceUriFields[1].compare(0, 2,
"25") == 0) {
283 return addrFields[0] == faceUriFields[0] &&
284 addrFields[1] == faceUriFields[1];
287 return checkAddressWithUri(addr, faceUri) && checkAddress(addr).first;
291 canonize(
const FaceUri& faceUri,
296 if (this->isCanonical(faceUri)) {
302 auto uri = std::make_shared<FaceUri>(faceUri);
303 boost::system::error_code ec;
304 auto ipAddress = boost::asio::ip::make_address(unescapeHost(faceUri.getHost()), ec);
307 if ((faceUri.getScheme() == m_v4Scheme && !ipAddress.is_v4()) ||
308 (faceUri.getScheme() == m_v6Scheme && !ipAddress.is_v6())) {
309 return onFailure(
"IPv4/v6 mismatch");
312 onDnsSuccess(uri, onSuccess, onFailure, ipAddress);
316 if (faceUri.getScheme() == m_v4Scheme) {
317 addressSelector = dns::Ipv4Only();
319 else if (faceUri.getScheme() == m_v6Scheme) {
320 addressSelector = dns::Ipv6Only();
323 BOOST_ASSERT(faceUri.getScheme() == m_baseScheme);
324 addressSelector = dns::AnyAddress();
328 [=] (
const auto& ipAddr) { onDnsSuccess(uri, onSuccess, onFailure, ipAddr); },
329 [=] (
const auto& reason) { onDnsFailure(uri, onFailure, reason); },
330 io, addressSelector, timeout);
336 IpHostCanonizeProvider(
const std::string& baseScheme,
337 uint16_t defaultUnicastPort = 6363,
338 uint16_t defaultMulticastPort = 56363)
339 : m_baseScheme(baseScheme)
340 , m_v4Scheme(baseScheme +
'4')
341 , m_v6Scheme(baseScheme +
'6')
342 , m_defaultUnicastPort(defaultUnicastPort)
343 , m_defaultMulticastPort(defaultMulticastPort)
349 onDnsSuccess(
const std::shared_ptr<FaceUri>& faceUri,
352 const boost::asio::ip::address& ipAddress)
const
354 auto [isOk, reason] = this->checkAddress(ipAddress);
356 return onFailure(reason);
360 if (faceUri->getPort().empty()) {
361 port = ipAddress.is_multicast() ? m_defaultMulticastPort : m_defaultUnicastPort;
365 port = boost::lexical_cast<uint16_t>(faceUri->getPort());
367 catch (
const boost::bad_lexical_cast&) {
368 return onFailure(
"invalid port number '" + faceUri->getPort() +
"'");
372 FaceUri canonicalUri(
typename Protocol::endpoint(ipAddress, port));
373 BOOST_ASSERT(canonicalUri.isCanonical());
374 onSuccess(canonicalUri);
378 onDnsFailure(
const std::shared_ptr<FaceUri>&,
380 const std::string& reason)
const
389 virtual std::pair<bool, std::string>
390 checkAddress(
const boost::asio::ip::address&)
const
396 unescapeHost(std::string host)
398 auto escapePos = host.find(
"%25");
399 if (escapePos != std::string::npos && escapePos < host.size() - 3) {
406 std::string m_baseScheme;
407 std::string m_v4Scheme;
408 std::string m_v6Scheme;
409 uint16_t m_defaultUnicastPort;
410 uint16_t m_defaultMulticastPort;
413 class UdpCanonizeProvider :
public IpHostCanonizeProvider<boost::asio::ip::udp>
416 UdpCanonizeProvider()
417 : IpHostCanonizeProvider(
"udp")
422 class TcpCanonizeProvider :
public IpHostCanonizeProvider<boost::asio::ip::tcp>
425 TcpCanonizeProvider()
426 : IpHostCanonizeProvider(
"tcp")
431 std::pair<bool, std::string>
432 checkAddress(
const boost::asio::ip::address& ipAddress)
const override
434 if (ipAddress.is_multicast()) {
435 return {
false,
"cannot use multicast address"};
441 class EtherCanonizeProvider :
public CanonizeProvider
444 std::set<std::string>
445 getSchemes()
const override
451 isCanonical(
const FaceUri& faceUri)
const override
453 if (!faceUri.getPort().empty()) {
456 if (!faceUri.getPath().empty()) {
461 return addr.toString() == faceUri.getHost();
465 canonize(
const FaceUri& faceUri,
472 return onFailure(
"invalid ethernet address '" + faceUri.getHost() +
"'");
475 FaceUri canonicalUri(addr);
476 BOOST_ASSERT(canonicalUri.isCanonical());
477 onSuccess(canonicalUri);
481 class DevCanonizeProvider :
public CanonizeProvider
484 std::set<std::string>
485 getSchemes()
const override
491 isCanonical(
const FaceUri& faceUri)
const override
493 return !faceUri.getHost().empty() && faceUri.getPort().empty() && faceUri.getPath().empty();
497 canonize(
const FaceUri& faceUri,
502 if (faceUri.getHost().empty()) {
503 onFailure(
"network interface name is missing");
506 if (!faceUri.getPort().empty()) {
507 onFailure(
"port number is not allowed");
510 if (!faceUri.getPath().empty() && faceUri.getPath() !=
"/") {
511 onFailure(
"path is not allowed");
516 BOOST_ASSERT(canonicalUri.isCanonical());
517 onSuccess(canonicalUri);
521 class UdpDevCanonizeProvider :
public CanonizeProvider
524 std::set<std::string>
525 getSchemes()
const override
527 return {
"udp4+dev",
"udp6+dev"};
531 isCanonical(
const FaceUri& faceUri)
const override
533 if (faceUri.getPort().empty()) {
536 if (!faceUri.getPath().empty()) {
543 canonize(
const FaceUri& faceUri,
548 if (this->isCanonical(faceUri)) {
552 onFailure(
"cannot canonize " + faceUri.toString());
559 EtherCanonizeProvider,
561 UdpDevCanonizeProvider>;
562 static_assert(boost::mp11::mp_is_set<CanonizeProviders>());
566 struct CanonizeProviderTableInitializer
568 template<
typename CP>
570 operator()(boost::mp11::mp_identity<CP>)
572 std::shared_ptr<CanonizeProvider> cp = std::make_shared<CP>();
573 auto schemes = cp->getSchemes();
574 BOOST_ASSERT(!schemes.empty());
576 for (
const auto& scheme : schemes) {
577 BOOST_ASSERT(m_providerTable.count(scheme) == 0);
578 m_providerTable[scheme] = cp;
585 static const CanonizeProvider*
586 getCanonizeProvider(
const std::string& scheme)
588 static const auto providerTable = [] {
589 using namespace boost::mp11;
591 mp_for_each<mp_transform<mp_identity, CanonizeProviders>>(CanonizeProviderTableInitializer{table});
595 auto it = providerTable.find(scheme);
596 return it == providerTable.end() ? nullptr : it->second.get();
602 return getCanonizeProvider(scheme) !=
nullptr;
608 const auto* cp = getCanonizeProvider(
getScheme());
613 return cp->isCanonical(*
this);
621 const auto* cp = getCanonizeProvider(
getScheme());
624 onFailure(
"scheme not supported");
630 onSuccess ? onSuccess : [] (
auto&&) {},
631 onFailure ? onFailure : [] (
auto&&) {},
The underlying protocol and address used by a Face.
std::string toString() const
Return string representation.
void canonize(const CanonizeSuccessCallback &onSuccess, const CanonizeFailureCallback &onFailure, boost::asio::io_context &io, time::nanoseconds timeout) const
Asynchronously convert this FaceUri to canonical form.
std::function< void(const std::string &reason)> CanonizeFailureCallback
static FaceUri fromDev(std::string_view ifname)
Construct a dev FaceUri from a network device name.
FaceUri()
Construct an empty FaceUri.
const std::string & getScheme() const
Get scheme (protocol)
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.
bool parse(std::string_view uri)
Exception-safe parsing.
std::function< void(const FaceUri &)> CanonizeSuccessCallback
static FaceUri fromUdpDev(const boost::asio::ip::udp::endpoint &endpoint, std::string_view ifname)
Construct a udp4 or udp6 NIC-associated FaceUri from endpoint and network device name.
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,...
Common includes and macros used throughout the library.
std::function< bool(const boost::asio::ip::address &)> AddressSelector
void asyncResolve(const std::string &host, const SuccessCallback &onSuccess, const ErrorCallback &onError, boost::asio::io_context &ioCtx, const AddressSelector &addressSelector, time::nanoseconds timeout)
Asynchronously resolve host.
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
boost::mp11::mp_list< UdpCanonizeProvider, TcpCanonizeProvider, EtherCanonizeProvider, DevCanonizeProvider, UdpDevCanonizeProvider > CanonizeProviders
std::map< std::string, std::shared_ptr< CanonizeProvider > > CanonizeProviderTable
std::string unescape(std::string_view str)
Decode a percent-encoded string.