From b9e25fa4e43a7ac403b12d72b3e4ae0d495cf3cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com>
Date: Tue, 1 Nov 2016 17:45:40 -0400
Subject: [PATCH] namedirectory: add cache, parse URI for nameserver

* Use .cache/ring/namecache to cache found sameservice mappings
* Parse URIs, use hostname as the nameserver (if any) or the default

Change-Id: Id7b16369359fa798d91c89a82917509d5990eae1
---
 src/client/configurationmanager.cpp |  8 ++-
 src/ringdht/namedirectory.cpp       | 87 +++++++++++++++++++++++++++--
 src/ringdht/namedirectory.h         | 16 ++++--
 src/ringdht/ringaccount.cpp         | 22 +++++---
 src/ringdht/ringaccount.h           |  1 +
 5 files changed, 113 insertions(+), 21 deletions(-)

diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp
index 97132fec0a..eb7b0e04d7 100644
--- a/src/client/configurationmanager.cpp
+++ b/src/client/configurationmanager.cpp
@@ -838,9 +838,13 @@ bool lookupName(const std::string& account, const std::string& nameserver, const
 {
 #if HAVE_RINGNS
     if (account.empty()) {
-        ring::NameDirectory::instance(nameserver).lookupName(name, [name](const std::string& result, ring::NameDirectory::Response response) {
+        auto cb = [name](const std::string& result, ring::NameDirectory::Response response) {
             ring::emitSignal<DRing::ConfigurationSignal::RegisteredNameFound>("", (int)response, result, name);
-        });
+        };
+        if (nameserver.empty())
+            ring::NameDirectory::lookupUri(name, "", cb);
+        else
+            ring::NameDirectory::instance(nameserver).lookupName(name, cb);
         return true;
     } else if (auto acc = ring::Manager::instance().getAccount<RingAccount>(account)) {
         acc->lookupName(name);
diff --git a/src/ringdht/namedirectory.cpp b/src/ringdht/namedirectory.cpp
index 3a65863db4..55afa13647 100644
--- a/src/ringdht/namedirectory.cpp
+++ b/src/ringdht/namedirectory.cpp
@@ -20,7 +20,9 @@
 #include "logger.h"
 #include "string_utils.h"
 #include "thread_pool.h"
+#include "fileutils.h"
 
+#include <msgpack.hpp>
 #include <json/json.h>
 #include <restbed>
 
@@ -28,13 +30,16 @@
 #include <ciso646>
 #include <sstream>
 #include <regex>
+#include <fstream>
 
 namespace ring {
 
 constexpr const char* const QUERY_NAME {"/name/"};
 constexpr const char* const QUERY_ADDR {"/addr/"};
+
+/** Parser for Ring URIs.         ( protocol        )    ( username         ) ( hostname                            ) */
+const std::regex URI_VALIDATOR {"^([a-zA-Z]+:(?://)?)?(?:([a-z0-9-_]{1,64})@)?([a-zA-Z0-9\\-._~%!$&'()*+,;=:\\[\\]]+)"};
 const std::regex NAME_VALIDATOR {"^[a-z0-9-_]{3,32}$"};
-const std::regex URI_VALIDATOR {"^(:?[a-zA-Z]+://)?([a-zA-Z0-9\\-._~%!$&'()*+,;=:\\[\\]]+)"};
 
 constexpr size_t MAX_RESPONSE_SIZE {1024 * 1024};
 
@@ -42,20 +47,50 @@ std::string hostFromUri(const std::string& uri)
 {
     std::smatch pieces_match;
     if (std::regex_search(uri, pieces_match, URI_VALIDATOR))
-        if (pieces_match.size() == 3)
-            return pieces_match[2].str();
+        if (pieces_match.size() == 4)
+            return pieces_match[3].str();
     return uri;
 }
 
-NameDirectory::NameDirectory(const std::string& s) : serverUri_(s), serverHost_(hostFromUri(s))
+void
+NameDirectory::lookupUri(const std::string& uri, const std::string& default_server, LookupCallback cb)
+{
+    RING_WARN("lookupUri: %s", uri.c_str());
+    std::smatch pieces_match;
+    if (std::regex_search(uri, pieces_match, URI_VALIDATOR)) {
+        if (pieces_match.size() == 4) {
+            if (pieces_match[2].length() == 0)
+                instance(default_server).lookupName(pieces_match[3], cb);
+            else
+                instance("http://"+pieces_match[3].str()).lookupName(pieces_match[2], cb);
+            return;
+        }
+    }
+    RING_ERR("Can't parse URI: %s", uri.c_str());
+    cb("", Response::error);
+}
+
+NameDirectory::NameDirectory(const std::string& s)
+   : serverUri_(s),
+     serverHost_(hostFromUri(s)),
+     cachePath_(fileutils::get_cache_dir()+DIR_SEPARATOR_STR+"namecache"+DIR_SEPARATOR_STR+serverHost_)
 {}
 
+void
+NameDirectory::load()
+{
+    loadCache();
+}
+
 NameDirectory& NameDirectory::instance(const std::string& server)
 {
     const std::string& s = server.empty() ? DEFAULT_SERVER_URI : server;
     static std::map<std::string, NameDirectory> instances {};
-    auto it = instances.emplace(s, NameDirectory{s});
-    return it.first->second;
+    auto r = instances.emplace(s, NameDirectory{s});
+    RING_WARN("NameDirectory: %s %p", s.c_str(), &r.first->second);
+    if (r.second)
+        r.first->second.load();
+    return r.first->second;
 }
 
 size_t getContentLength(restbed::Response& reply)
@@ -108,6 +143,7 @@ void NameDirectory::lookupAddress(const std::string& addr, LookupCallback cb)
                 addrCache_.emplace(name, addr);
                 nameCache_.emplace(addr, name);
                 cb(name, Response::found);
+                saveCache();
             } else {
                 cb("", Response::notFound);
             }
@@ -172,6 +208,7 @@ void NameDirectory::lookupName(const std::string& name, LookupCallback cb)
                 addrCache_.emplace(name, addr);
                 nameCache_.emplace(addr, name);
                 cb(addr, Response::found);
+                saveCache();
             } else {
                 cb("", Response::notFound);
             }
@@ -265,4 +302,42 @@ void NameDirectory::registerName(const std::string& addr, const std::string& nam
     ThreadPool::instance().run([ret](){ ret.get(); });
 }
 
+void
+NameDirectory::saveCache()
+{
+    fileutils::recursive_mkdir(fileutils::get_cache_dir()+DIR_SEPARATOR_STR+"namecache");
+    std::ofstream file(cachePath_, std::ios::trunc);
+    msgpack::pack(file, nameCache_);
+    RING_DBG("Saved %lu name-address mappings", (long unsigned)nameCache_.size());
+}
+
+void
+NameDirectory::loadCache()
+{
+    msgpack::unpacker pac;
+
+    // read file
+    {
+        std::ifstream file(cachePath_);
+        if (!file.is_open()) {
+            RING_DBG("Could not load %s", cachePath_.c_str());
+            return;
+        }
+        std::string line;
+        while (std::getline(file, line)) {
+            pac.reserve_buffer(line.size());
+            memcpy(pac.buffer(), line.data(), line.size());
+            pac.buffer_consumed(line.size());
+        }
+    }
+
+    // load values
+    msgpack::object_handle oh;
+    if (pac.next(oh))
+        oh.get().convert(nameCache_);
+    for (const auto& m : nameCache_)
+        addrCache_.emplace(m.second, m.first);
+    RING_DBG("Loaded %lu name-address mappings", (long unsigned)nameCache_.size());
+}
+
 }
diff --git a/src/ringdht/namedirectory.h b/src/ringdht/namedirectory.h
index e8acc6f2a4..fe6b923bed 100644
--- a/src/ringdht/namedirectory.h
+++ b/src/ringdht/namedirectory.h
@@ -26,20 +26,24 @@ namespace ring {
 class NameDirectory
 {
 public:
+    enum class Response : int { found = 0, invalidName, notFound, error };
+    enum class RegistrationResponse : int { success = 0, invalidName, alreadyTaken, error };
+
+    using LookupCallback = std::function<void(const std::string& result, Response response)>;
+    using RegistrationCallback = std::function<void(RegistrationResponse response)>;
+
     NameDirectory() {}
     NameDirectory(const std::string& s);
+    void load();
 
     static NameDirectory& instance(const std::string& server);
     static NameDirectory& instance() { return instance(DEFAULT_SERVER_URI); }
 
-    enum class Response : int { found = 0, invalidName, notFound, error };
-    enum class RegistrationResponse : int { success = 0, invalidName, alreadyTaken, error };
+    static void lookupUri(const std::string& uri, const std::string& default_server, LookupCallback cb);
 
-    using LookupCallback = std::function<void(const std::string& result, Response response)>;
     void lookupAddress(const std::string& addr, LookupCallback cb);
     void lookupName(const std::string& name, LookupCallback cb);
 
-    using RegistrationCallback = std::function<void(RegistrationResponse response)>;
     void registerName(const std::string& addr, const std::string& name, const std::string& owner, RegistrationCallback cb);
 
     const std::string& getServer() const {
@@ -54,8 +58,12 @@ private:
     std::map<std::string, std::string> nameCache_;
     std::map<std::string, std::string> addrCache_;
 
+    const std::string cachePath_;
+
     bool validateName(const std::string& name) const;
 
+    void saveCache();
+    void loadCache();
 };
 
 }
diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp
index a72a5d0faa..808ce79cc5 100644
--- a/src/ringdht/ringaccount.cpp
+++ b/src/ringdht/ringaccount.cpp
@@ -235,7 +235,7 @@ RingAccount::newOutgoingCall(const std::string& toUrl)
     } catch (...) {
 #if HAVE_RINGNS
         std::weak_ptr<RingAccount> wthis_ = std::static_pointer_cast<RingAccount>(shared_from_this());
-        nameDir_.get().lookupName(sufix, [wthis_,call](const std::string& result, NameDirectory::Response response) mutable {
+        NameDirectory::lookupUri(sufix, nameServer_, [wthis_,call](const std::string& result, NameDirectory::Response response) mutable {
             runOnMainThread([=]() mutable {
                 if (auto sthis = wthis_.lock()) {
                     try {
@@ -571,9 +571,13 @@ void RingAccount::unserialize(const YAML::Node &node)
     dhtPortUsed_ = dhtPort_;
 
 #if HAVE_RINGNS
-    std::string ringns_server;
-    parseValue(node, DRing::Account::ConfProperties::RingNS::URI, ringns_server);
-    nameDir_ = NameDirectory::instance(ringns_server);
+    try {
+        //std::string ringns_server;
+        parseValue(node, DRing::Account::ConfProperties::RingNS::URI, nameServer_);
+        nameDir_ = NameDirectory::instance(nameServer_);
+    } catch (const std::exception& e) {
+        RING_WARN("can't read name server: %s", e.what());
+    }
 #endif
 
     parseValue(node, Conf::DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_);
@@ -1169,9 +1173,9 @@ RingAccount::setAccountDetails(const std::map<std::string, std::string> &details
     parseString(details, DRing::Account::ConfProperties::ARCHIVE_PATH,     archivePath_);
 
 #if HAVE_RINGNS
-    std::string ringns_server;
-    parseString(details, DRing::Account::ConfProperties::RingNS::URI,     ringns_server);
-    nameDir_ = NameDirectory::instance(ringns_server);
+    //std::string ringns_server;
+    parseString(details, DRing::Account::ConfProperties::RingNS::URI,     nameServer_);
+    nameDir_ = NameDirectory::instance(nameServer_);
 #endif
 
     loadAccount(archive_password, archive_pin);
@@ -1206,7 +1210,7 @@ RingAccount::getAccountDetails() const
     //a.emplace(DRing::Account::ConfProperties::ETH::KEY_FILE,               ethPath_);
     a.emplace(DRing::Account::ConfProperties::RingNS::ACCOUNT,               ethAccount_);
 #if HAVE_RINGNS
-    a.emplace(DRing::Account::ConfProperties::RingNS::URI,                  nameDir_.get().getServer());
+    a.emplace(DRing::Account::ConfProperties::RingNS::URI,                   nameServer_);
 #endif
 
     return a;
@@ -1229,7 +1233,7 @@ void
 RingAccount::lookupName(const std::string& name)
 {
     auto acc = getAccountID();
-    nameDir_.get().lookupName(name, [acc,name](const std::string& result, NameDirectory::Response response) {
+    NameDirectory::lookupUri(name, nameServer_, [acc,name](const std::string& result, NameDirectory::Response response) {
         emitSignal<DRing::ConfigurationSignal::RegisteredNameFound>(acc, (int)response, result, name);
     });
 }
diff --git a/src/ringdht/ringaccount.h b/src/ringdht/ringaccount.h
index 82dc120c66..3c5ad43bc7 100644
--- a/src/ringdht/ringaccount.h
+++ b/src/ringdht/ringaccount.h
@@ -353,6 +353,7 @@ class RingAccount : public SIPAccountBase {
 
 #if HAVE_RINGNS
         std::reference_wrapper<NameDirectory> nameDir_;
+        std::string nameServer_;
         std::string registeredName_;
 #endif
 
-- 
GitLab