31#include <opendht/crypto.h>
32#include <opendht/utils.h>
33#include <opendht/http.h>
34#include <opendht/logger.h>
35#include <opendht/thread_pool.h>
67const std::regex
URI_VALIDATOR {R
"(^([a-zA-Z]+:(?://)?)?(?:([\w\-.~%!$&'()*+,;=]{1,64}|[^\s@]{1,64})@)?([^\s@]+)$)"};
76 std::transform(
string.begin(),
string.end(),
string.begin(),
::tolower);
106 , httpContext_(
Manager::instance().ioContext())
107 , saveTask_(*httpContext_)
109 if (!serverUrl_.empty() && serverUrl_.back() ==
'/')
110 serverUrl_.pop_back();
111 resolver_ = std::make_shared<dht::http::Resolver>(*httpContext_,
serverUrl, logger_);
119 std::lock_guard
lk(requestsMtx_);
135 std::string name = url;
136 std::transform(name.begin(), name.end(), name.begin(),
::tolower);
137 if (name.find(
"://") == std::string::npos)
138 name =
"https://" + name;
149 static std::map<std::string, NameDirectory>
instances {};
153 auto r =
instances.emplace(std::piecewise_construct, std::forward_as_tuple(s), std::forward_as_tuple(s,
l));
155 r.first->second.load();
156 return r.first->second;
160NameDirectory::setHeaderFields(
Request& request)
162 request.set_header_field(restinio::http_field_t::user_agent,
164 request.set_header_field(restinio::http_field_t::accept,
"*/*");
165 request.set_header_field(restinio::http_field_t::content_type,
"application/json");
176 auto request = std::make_shared<Request>(*httpContext_, resolver_, serverUrl_ +
QUERY_ADDR + addr);
178 request->set_method(restinio::http_method_get());
179 setHeaderFields(*request);
180 request->add_on_done_callback([
this,
cb = std::move(
cb), addr](
const dht::http::Response& response) {
181 if (response.status_code > 400 && response.status_code < 500) {
182 auto cacheResult = nameCache(addr);
183 if (not cacheResult.first.empty())
184 cb(cacheResult.first, cacheResult.second, Response::found);
186 cb(
"",
"", Response::notFound);
187 }
else if (response.status_code == 400)
189 else if (response.status_code != 200) {
190 JAMI_ERROR(
"Address lookup for {} on {} failed with code={}", addr, serverUrl_, response.status_code);
191 cb(
"",
"", Response::error);
195 if (!json::parse(response.body, json)) {
196 cb(
"",
"", Response::error);
199 auto name = json[
"name"].asString();
201 cb(name, addr, Response::notFound);
204 JAMI_DEBUG(
"Found name for {}: {}", addr, name);
206 std::lock_guard l(cacheLock_);
207 addrCache_.emplace(name, std::pair(name, addr));
208 nameCache_.emplace(addr, std::pair(name, addr));
211 cb(name, addr, Response::found);
212 }
catch (
const std::exception&
e) {
213 JAMI_ERROR(
"Error when performing address lookup: {}",
e.what());
217 std::lock_guard
lk(requestsMtx_);
218 if (
auto req = response.request.lock())
219 requests_.erase(req);
222 std::lock_guard
lk(requestsMtx_);
223 requests_.emplace(request);
226 }
catch (
const std::exception&
e) {
227 JAMI_ERROR(
"Error when performing address lookup: {}",
e.what());
228 std::lock_guard
lk(requestsMtx_);
230 requests_.erase(request);
235NameDirectory::verify(
const std::string& name,
const dht::crypto::PublicKey& pk,
const std::string& signature)
237 return pk.checkSignature(std::vector<uint8_t>(name.begin(), name.end()), base64::decode(signature));
243 auto cacheResult = addrCache(name);
244 if (not cacheResult.first.empty()) {
245 cb(cacheResult.first, cacheResult.second, Response::found);
249 auto request = std::make_shared<Request>(*httpContext_, resolver_, serverUrl_ +
QUERY_NAME + encodedName);
251 request->set_method(restinio::http_method_get());
252 setHeaderFields(*request);
253 request->add_on_done_callback([
this, name, cb = std::move(cb)](
const dht::http::Response& response) {
254 if (response.status_code > 400 && response.status_code < 500)
255 cb(
"",
"", Response::notFound);
256 else if (response.status_code == 400)
257 cb(
"",
"", Response::invalidResponse);
258 else if (response.status_code < 200 || response.status_code > 299) {
259 JAMI_ERROR(
"Name lookup for {} on {} failed with code={}", name, serverUrl_, response.status_code);
260 cb(
"",
"", Response::error);
264 if (!json::parse(response.body, json)) {
265 cb(
"",
"", Response::error);
268 auto nameResult = json[
"name"].asString();
269 auto addr = json[
"addr"].asString();
270 auto publickey = json[
"publickey"].asString();
271 auto signature = json[
"signature"].asString();
273 if (starts_with(addr, HEX_PREFIX))
274 addr = addr.substr(HEX_PREFIX.size());
276 cb(
"",
"", Response::notFound);
279 if (not publickey.empty() and not signature.empty()) {
281 auto pk = dht::crypto::PublicKey(base64::decode(publickey));
282 if (pk.getId().toString() != addr or not verify(nameResult, pk, signature)) {
283 cb(
"",
"", Response::invalidResponse);
286 } catch (const std::exception& e) {
287 cb(
"",
"", Response::invalidResponse);
291 JAMI_DEBUG(
"Found address for {}: {}", name, addr);
293 std::lock_guard l(cacheLock_);
294 addrCache_.emplace(name, std::pair(nameResult, addr));
295 addrCache_.emplace(nameResult, std::pair(nameResult, addr));
296 nameCache_.emplace(addr, std::pair(nameResult, addr));
299 cb(nameResult, addr, Response::found);
300 } catch (
const std::exception& e) {
301 JAMI_ERROR(
"Error when performing name lookup: {}", e.what());
302 cb(
"",
"", Response::error);
305 if (
auto req = response.request.lock())
306 requests_.erase(req);
309 std::lock_guard lk(requestsMtx_);
310 requests_.emplace(request);
313 }
catch (
const std::exception& e) {
314 JAMI_ERROR(
"Name lookup for {} failed: {}", name, e.what());
315 std::lock_guard lk(requestsMtx_);
317 requests_.erase(request);
321using Blob = std::vector<uint8_t>;
323NameDirectory::registerName(
const std::string& addr,
324 const std::string& n,
325 const std::string& owner,
327 const std::string& signedname,
328 const std::string& publickey)
330 std::string name {n};
332 auto cacheResult = addrCache(name);
333 if (not cacheResult.first.empty()) {
334 if (cacheResult.second == addr)
335 cb(RegistrationResponse::success, name);
337 cb(RegistrationResponse::alreadyTaken, name);
341 std::lock_guard l(cacheLock_);
342 if (not pendingRegistrations_.emplace(addr, name).second) {
343 JAMI_WARNING(
"RegisterName: already registering name {} {}", addr, name);
344 cb(RegistrationResponse::error, name);
348 std::string body = fmt::format(
"{{\"addr\":\"{}\",\"owner\":\"{}\",\"signature\":\"{}\",\"publickey\":\"{}\"}}",
352 base64::encode(publickey));
355 auto request = std::make_shared<Request>(*httpContext_, resolver_, serverUrl_ +
QUERY_NAME + encodedName);
357 request->set_method(restinio::http_method_post());
358 setHeaderFields(*request);
359 request->set_body(body);
361 JAMI_WARNING(
"RegisterName: sending request {} {}", addr, name);
363 request->add_on_done_callback([
this, name, addr, cb = std::move(cb)](
const dht::http::Response& response) {
365 std::lock_guard l(cacheLock_);
366 pendingRegistrations_.erase(name);
368 if (response.status_code == 400) {
369 cb(RegistrationResponse::incompleteRequest, name);
370 JAMI_ERROR(
"RegistrationResponse::incompleteRequest");
371 }
else if (response.status_code == 401) {
372 cb(RegistrationResponse::signatureVerificationFailed, name);
373 JAMI_ERROR(
"RegistrationResponse::signatureVerificationFailed");
374 }
else if (response.status_code == 403) {
375 cb(RegistrationResponse::alreadyTaken, name);
376 JAMI_ERROR(
"RegistrationResponse::alreadyTaken");
377 }
else if (response.status_code == 409) {
378 cb(RegistrationResponse::alreadyTaken, name);
379 JAMI_ERROR(
"RegistrationResponse::alreadyTaken");
380 }
else if (response.status_code > 400 && response.status_code < 500) {
381 cb(RegistrationResponse::alreadyTaken, name);
382 JAMI_ERROR(
"RegistrationResponse::alreadyTaken");
383 }
else if (response.status_code < 200 || response.status_code > 299) {
384 cb(RegistrationResponse::error, name);
389 Json::CharReaderBuilder rbuilder;
391 auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
392 if (!reader->parse(response.body.data(), response.body.data() + response.body.size(), &json, &err)) {
393 cb(RegistrationResponse::error, name);
396 auto success = json[
"success"].asBool();
397 JAMI_DEBUG(
"Got reply for registration of {} {}: {}", name, addr, success ?
"success" :
"failure");
399 std::lock_guard l(cacheLock_);
400 addrCache_.emplace(name, std::pair(name, addr));
401 nameCache_.emplace(addr, std::pair(name, addr));
403 cb(success ? RegistrationResponse::success : RegistrationResponse::error, name);
405 std::lock_guard lk(requestsMtx_);
406 if (
auto req = response.request.lock())
407 requests_.erase(req);
410 std::lock_guard lk(requestsMtx_);
411 requests_.emplace(request);
414 }
catch (
const std::exception& e) {
415 JAMI_ERROR(
"Error when performing name registration: {}", e.what());
416 cb(RegistrationResponse::error, name);
418 std::lock_guard l(cacheLock_);
419 pendingRegistrations_.erase(name);
421 std::lock_guard lk(requestsMtx_);
423 requests_.erase(request);
428NameDirectory::scheduleCacheSave()
430 saveTask_.expires_after(SAVE_INTERVAL);
431 saveTask_.async_wait([
this](
const asio::error_code& ec) {
439NameDirectory::saveCache()
441 dhtnet::fileutils::recursive_mkdir(fileutils::get_cache_dir() / CACHE_DIRECTORY);
442 std::lock_guard lock(dhtnet::fileutils::getFileLock(cachePath_));
443 std::ofstream file(cachePath_, std::ios::trunc | std::ios::binary);
444 if (!file.is_open()) {
445 JAMI_ERROR(
"Unable to save cache to {}", cachePath_);
449 std::lock_guard l(cacheLock_);
450 msgpack::pack(file, nameCache_);
452 JAMI_DEBUG(
"Saved {:d} name-address mapping(s) to {}", nameCache_.size(), cachePath_);
456NameDirectory::loadCache()
458 msgpack::unpacker pac;
462 std::lock_guard lock(dhtnet::fileutils::getFileLock(cachePath_));
463 std::ifstream file(cachePath_);
464 if (!file.is_open()) {
469 while (std::getline(file, line)) {
470 pac.reserve_buffer(line.size());
471 memcpy(pac.buffer(), line.data(), line.size());
472 pac.buffer_consumed(line.size());
478 std::lock_guard l(cacheLock_);
479 msgpack::object_handle oh;
481 oh.get().convert(nameCache_);
482 for (
const auto& m : nameCache_)
483 addrCache_.emplace(m.second.second, m.second);
484 }
catch (
const msgpack::parse_error& e) {
485 JAMI_ERROR(
"Error when parsing msgpack object: {}", e.what());
486 }
catch (
const std::bad_cast& e) {
487 JAMI_ERROR(
"Error when loading cache: {}", e.what());
490 JAMI_DEBUG(
"Loaded {:d} name-address mapping(s) from cache", nameCache_.size());
Manager (controller) of daemon.
std::function< void(RegistrationResponse response, const std::string &name)> RegistrationCallback
void lookupName(const std::string &name, LookupCallback cb)
void lookupAddress(const std::string &addr, LookupCallback cb)
static void lookupUri(std::string_view uri, const std::string &default_server, LookupCallback cb)
std::function< void(const std::string &name, const std::string &address, Response response)> LookupCallback
static NameDirectory & instance()
NameDirectory(const std::string &serverUrl, std::shared_ptr< dht::Logger > l={})
#define JAMI_ERROR(formatstr,...)
#define JAMI_DEBUG(formatstr,...)
#define JAMI_WARNING(formatstr,...)
const std::filesystem::path & get_cache_dir()
constexpr const char *const QUERY_ADDR
std::string canonicalName(const std::string &url)
constexpr std::string_view platform()
void emitSignal(Args... args)
const std::regex URI_VALIDATOR
constexpr size_t MAX_RESPONSE_SIZE
constexpr auto CACHE_DIRECTORY
constexpr std::string_view HEX_PREFIX
constexpr const char DEFAULT_SERVER_HOST[]
void toLower(std::string &string)
std::string urlEncode(std::string_view input)
Percent-encode a string according to RFC 3986 unreserved characters.
std::vector< uint8_t > Blob
constexpr const char *const QUERY_NAME
constexpr std::chrono::seconds SAVE_INTERVAL
constexpr std::string_view arch()
bool regex_match(string_view sv, svmatch &m, const regex &e, regex_constants::match_flag_type flags=regex_constants::match_default)
match_results< string_view::const_iterator > svmatch