util: FaceUri canonization for udp,tcp,ether
refs #1994
Change-Id: I5349d999a3dd52a1fe533e17766d70df5e67091f
diff --git a/src/util/face-uri.cpp b/src/util/face-uri.cpp
index e5f9611..ff5669b 100644
--- a/src/util/face-uri.cpp
+++ b/src/util/face-uri.cpp
@@ -26,10 +26,14 @@
*/
#include "face-uri.hpp"
+#include "dns.hpp"
+#include <set>
#include <boost/concept_check.hpp>
#include <boost/regex.hpp>
#include <boost/lexical_cast.hpp>
+#include <boost/mpl/vector.hpp>
+#include <boost/mpl/for_each.hpp>
namespace ndn {
namespace util {
@@ -202,5 +206,340 @@
return os;
}
+/** \brief a CanonizeProvider provides FaceUri canonization functionality for a group of schemes
+ */
+class CanonizeProvider : noncopyable
+{
+public:
+ virtual
+ ~CanonizeProvider()
+ {
+ }
+
+ virtual std::set<std::string>
+ getSchemes() const = 0;
+
+ virtual bool
+ isCanonical(const FaceUri& faceUri) const = 0;
+
+ virtual void
+ canonize(const FaceUri& faceUri,
+ const FaceUri::CanonizeSuccessCallback& onSuccess,
+ const FaceUri::CanonizeFailureCallback& onFailure,
+ boost::asio::io_service& io, const time::nanoseconds& timeout) const = 0;
+};
+
+template<typename Protocol>
+class IpHostCanonizeProvider : public CanonizeProvider
+{
+public:
+ virtual std::set<std::string>
+ getSchemes() const
+ {
+ std::set<std::string> schemes;
+ schemes.insert(m_baseScheme);
+ schemes.insert(m_v4Scheme);
+ schemes.insert(m_v6Scheme);
+ return schemes;
+ }
+
+ virtual bool
+ isCanonical(const FaceUri& faceUri) const
+ {
+ if (faceUri.getPort().empty()) {
+ return false;
+ }
+ if (!faceUri.getPath().empty()) {
+ return false;
+ }
+
+ boost::system::error_code ec;
+ boost::asio::ip::address addr;
+ if (faceUri.getScheme() == m_v4Scheme) {
+ addr = boost::asio::ip::address_v4::from_string(faceUri.getHost(), ec);
+ }
+ else if (faceUri.getScheme() == m_v6Scheme) {
+ addr = boost::asio::ip::address_v6::from_string(faceUri.getHost(), ec);
+ }
+ else {
+ return false;
+ }
+ return !static_cast<bool>(ec) && addr.to_string() == faceUri.getHost() &&
+ this->checkAddress(addr).first;
+ }
+
+ virtual void
+ canonize(const FaceUri& faceUri,
+ const FaceUri::CanonizeSuccessCallback& onSuccess,
+ const FaceUri::CanonizeFailureCallback& onFailure,
+ boost::asio::io_service& io, const time::nanoseconds& timeout) const
+ {
+ if (this->isCanonical(faceUri)) {
+ onSuccess(faceUri);
+ return;
+ }
+
+ dns::AddressSelector addressSelector;
+ if (faceUri.getScheme() == m_v4Scheme) {
+ addressSelector = dns::Ipv4Only();
+ }
+ else if (faceUri.getScheme() == m_v6Scheme) {
+ addressSelector = dns::Ipv6Only();
+ }
+ else {
+ BOOST_ASSERT(faceUri.getScheme() == m_baseScheme);
+ addressSelector = dns::AnyAddress();
+ }
+
+ // make a copy because caller may modify faceUri
+ shared_ptr<FaceUri> uri = make_shared<FaceUri>(faceUri);
+ dns::asyncResolve(faceUri.getHost(),
+ bind(&IpHostCanonizeProvider<Protocol>::onDnsSuccess, this, uri, onSuccess, onFailure, _1),
+ bind(&IpHostCanonizeProvider<Protocol>::onDnsFailure, this, uri, onFailure, _1),
+ io, addressSelector, timeout);
+ }
+
+protected:
+ IpHostCanonizeProvider(const std::string& baseScheme,
+ uint32_t defaultUnicastPort = 6363,
+ uint32_t defaultMulticastPort = 56363)
+ : m_baseScheme(baseScheme)
+ , m_v4Scheme(baseScheme + "4")
+ , m_v6Scheme(baseScheme + "6")
+ , m_defaultUnicastPort(defaultUnicastPort)
+ , m_defaultMulticastPort(defaultMulticastPort)
+ {
+ }
+
+private:
+ // faceUri is a shared_ptr passed by value because this function can take ownership
+ void
+ onDnsSuccess(shared_ptr<FaceUri> faceUri,
+ const FaceUri::CanonizeSuccessCallback& onSuccess,
+ const FaceUri::CanonizeFailureCallback& onFailure,
+ const dns::IpAddress& ipAddress) const
+ {
+ std::pair<bool, std::string> checkAddressRes = this->checkAddress(ipAddress);
+ if (!checkAddressRes.first) {
+ onFailure(checkAddressRes.second);
+ return;
+ }
+
+ uint32_t port = 0;
+ if (faceUri->getPort().empty()) {
+ port = ipAddress.is_multicast() ? m_defaultMulticastPort : m_defaultUnicastPort;
+ }
+ else {
+ try {
+ port = boost::lexical_cast<uint32_t>(faceUri->getPort());
+ }
+ catch (boost::bad_lexical_cast&) {
+ onFailure("invalid port number");
+ return;
+ }
+ }
+
+ FaceUri canonicalUri(typename Protocol::endpoint(ipAddress, port));
+ BOOST_ASSERT(canonicalUri.isCanonical());
+ onSuccess(canonicalUri);
+ }
+
+ // faceUri is a shared_ptr passed by value because this function can take ownership
+ void
+ onDnsFailure(shared_ptr<FaceUri> faceUri, const FaceUri::CanonizeFailureCallback& onFailure,
+ const std::string& reason) const
+ {
+ onFailure(reason);
+ }
+
+ /** \brief when overriden in a subclass, check the IP address is allowable
+ * \return (true,ignored) if the address is allowable;
+ * (false,reason) if the address is not allowable.
+ */
+ virtual std::pair<bool, std::string>
+ checkAddress(const dns::IpAddress& ipAddress) const
+ {
+ return std::make_pair(true, "");
+ }
+
+private:
+ std::string m_baseScheme;
+ std::string m_v4Scheme;
+ std::string m_v6Scheme;
+ uint32_t m_defaultUnicastPort;
+ uint32_t m_defaultMulticastPort;
+};
+
+class UdpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::udp>
+{
+public:
+ UdpCanonizeProvider()
+ : IpHostCanonizeProvider("udp")
+ {
+ }
+
+protected:
+ // checkAddress is not overriden:
+ // Although NFD doesn't support IPv6 multicast, it's an implementation limitation.
+ // FaceMgmt protocol allows IPv6 multicast address in UDP.
+};
+
+class TcpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::tcp>
+{
+public:
+public:
+ TcpCanonizeProvider()
+ : IpHostCanonizeProvider("tcp")
+ {
+ }
+
+protected:
+ virtual std::pair<bool, std::string>
+ checkAddress(const dns::IpAddress& ipAddress) const
+ {
+ if (ipAddress.is_multicast()) {
+ return std::make_pair(false, "cannot use multicast address");
+ }
+ return std::make_pair(true, "");
+ }
+};
+
+class EtherCanonizeProvider : public CanonizeProvider
+{
+public:
+ virtual std::set<std::string>
+ getSchemes() const
+ {
+ std::set<std::string> schemes;
+ schemes.insert("ether");
+ return schemes;
+ }
+
+ virtual bool
+ isCanonical(const FaceUri& faceUri) const
+ {
+ if (!faceUri.getPort().empty()) {
+ return false;
+ }
+ if (!faceUri.getPath().empty()) {
+ return false;
+ }
+
+ ethernet::Address addr = ethernet::Address::fromString(faceUri.getHost());
+ return addr.toString() == faceUri.getHost();
+ }
+
+ virtual void
+ canonize(const FaceUri& faceUri,
+ const FaceUri::CanonizeSuccessCallback& onSuccess,
+ const FaceUri::CanonizeFailureCallback& onFailure,
+ boost::asio::io_service& io, const time::nanoseconds& timeout) const
+ {
+ ethernet::Address addr = ethernet::Address::fromString(faceUri.getHost());
+ if (addr.isNull()) {
+ onFailure("cannot parse address");
+ return;
+ }
+
+ FaceUri canonicalUri(addr);
+ BOOST_ASSERT(canonicalUri.isCanonical());
+ onSuccess(canonicalUri);
+ }
+};
+
+typedef boost::mpl::vector<
+ UdpCanonizeProvider*,
+ TcpCanonizeProvider*,
+ EtherCanonizeProvider*
+ > CanonizeProviders;
+typedef std::map<std::string, shared_ptr<CanonizeProvider> > CanonizeProviderTable;
+
+class CanonizeProviderTableInitializer
+{
+public:
+ explicit
+ CanonizeProviderTableInitializer(CanonizeProviderTable& providerTable)
+ : m_providerTable(providerTable)
+ {
+ }
+
+ template<typename CP> void
+ operator()(CP*)
+ {
+ shared_ptr<CanonizeProvider> cp = make_shared<CP>();
+
+ std::set<std::string> schemes = cp->getSchemes();
+ BOOST_ASSERT(!schemes.empty());
+ for (std::set<std::string>::iterator it = schemes.begin();
+ it != schemes.end(); ++it) {
+ BOOST_ASSERT(m_providerTable.count(*it) == 0);
+ m_providerTable[*it] = cp;
+ }
+ }
+
+private:
+ CanonizeProviderTable& m_providerTable;
+};
+
+static const CanonizeProvider*
+getCanonizeProvider(const std::string& scheme)
+{
+ static CanonizeProviderTable providerTable;
+ if (providerTable.empty()) {
+ boost::mpl::for_each<CanonizeProviders>(CanonizeProviderTableInitializer(providerTable));
+ BOOST_ASSERT(!providerTable.empty());
+ }
+
+ CanonizeProviderTable::const_iterator it = providerTable.find(scheme);
+ if (it == providerTable.end()) {
+ return 0;
+ }
+ return it->second.get();
+}
+
+bool
+FaceUri::canCanonize(const std::string& scheme)
+{
+ return getCanonizeProvider(scheme) != 0;
+}
+
+bool
+FaceUri::isCanonical() const
+{
+ const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
+ if (cp == 0) {
+ return false;
+ }
+
+ return cp->isCanonical(*this);
+}
+
+static inline void
+nop()
+{
+}
+
+void
+FaceUri::canonize(const CanonizeSuccessCallback& onSuccess,
+ const CanonizeFailureCallback& onFailure,
+ boost::asio::io_service& io, const time::nanoseconds& timeout) const
+{
+ const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
+ if (cp == 0) {
+ if (static_cast<bool>(onFailure)) {
+ onFailure("scheme not supported");
+ }
+ return;
+ }
+
+ static CanonizeSuccessCallback successNop = bind(&nop);
+ static CanonizeFailureCallback failureNop = bind(&nop);
+
+ cp->canonize(*this,
+ static_cast<bool>(onSuccess) ? onSuccess : successNop,
+ static_cast<bool>(onFailure) ? onFailure : failureNop,
+ io, timeout);
+}
+
} // namespace util
} // namespace ndn
diff --git a/src/util/face-uri.hpp b/src/util/face-uri.hpp
index f2786e1..95d2c87 100644
--- a/src/util/face-uri.hpp
+++ b/src/util/face-uri.hpp
@@ -33,6 +33,7 @@
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/local/stream_protocol.hpp>
#include "ethernet.hpp"
+#include "time.hpp"
namespace ndn {
namespace util {
@@ -142,6 +143,36 @@
bool
operator!=(const FaceUri& rhs) const;
+public: // canonical FaceUri
+ /** \return whether a FaceUri of the scheme can be canonized
+ */
+ static bool
+ canCanonize(const std::string& scheme);
+
+ /** \brief determine whether this FaceUri is in canonical form
+ * \return true if this FaceUri is in canonical form,
+ * false if this FaceUri is not in canonical form or
+ * or it's undetermined whether this FaceUri is in canonical form
+ */
+ bool
+ isCanonical() const;
+
+ typedef function<void(const FaceUri&)> CanonizeSuccessCallback;
+ typedef function<void(const std::string& reason)> CanonizeFailureCallback;
+
+ /** \brief asynchronously convert this FaceUri to canonical form
+ * \param onSuccess function to call after this FaceUri is converted to canonical form
+ * \note A new FaceUri in canonical form will be created; this FaceUri is unchanged.
+ * \param onFailure function to call if this FaceUri cannot be converted to canonical form
+ * \param timeout maximum allowable duration of the operations.
+ * It's intentional not to provide a default value: the caller should set
+ * a reasonable value in balance between network delay and user experience.
+ */
+ void
+ canonize(const CanonizeSuccessCallback& onSuccess,
+ const CanonizeFailureCallback& onFailure,
+ boost::asio::io_service& io, const time::nanoseconds& timeout) const;
+
private:
std::string m_scheme;
std::string m_host;