New Upstream Release - opendht

Ready changes

Summary

Merged new upstream version: 2.6.0rc1 (was: 2.4.12).

Diff

diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml
index 062ad2b..2dd7988 100644
--- a/.github/workflows/ccpp.yml
+++ b/.github/workflows/ccpp.yml
@@ -4,55 +4,63 @@ on: [push, pull_request]
 
 jobs:
   build-ubuntu:
-    name: Ubuntu/GCC build
+    name: Ubuntu/GCC Autotools build
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v3
-    - name: deps
+    - name: Dependencies
       run: |
-        sudo apt install libncurses5-dev libreadline-dev nettle-dev \
+        sudo apt install libncurses5-dev libreadline-dev nettle-dev libasio-dev \
         libgnutls28-dev libuv1-dev cython3 python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev \
         autotools-dev autoconf libfmt-dev libhttp-parser-dev libmsgpack-dev libargon2-0-dev
-    - name: autogen
-      run: ./autogen.sh
-    - name: asio
+    - name: Configure
       run: |
-        wget https://github.com/aberaud/asio/archive/b2b7a1c166390459e1c169c8ae9ef3234b361e3f.tar.gz \
-        && tar -xvf b2b7a1c166390459e1c169c8ae9ef3234b361e3f.tar.gz && cd asio-b2b7a1c166390459e1c169c8ae9ef3234b361e3f/asio \
-        && ./autogen.sh && ./configure --prefix=/usr --without-boost --disable-examples --disable-tests  \
-        && sudo make install \
-        && cd ../../ && rm -rf asio*
-    - name: configure
-      run: ./configure
-    - name: make
+        ./autogen.sh
+        ./configure
+    - name: Build
       run: make
-    - name: make check
-      run: make check
+
+  build-ubuntu-meson:
+    name: Ubuntu/GCC Meson build
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v3
+    - name: Dependencies
+      run: |
+        sudo apt install meson ninja-build libncurses5-dev libreadline-dev nettle-dev libasio-dev \
+        libgnutls28-dev libuv1-dev cython3 python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev \
+        libfmt-dev libhttp-parser-dev libmsgpack-dev libargon2-0-dev
+    - name: Configure
+      run: meson setup build .
+    - name: Build
+      run: cd build && ninja
 
   build-ubuntu-minimal:
     name: Ubuntu/GCC minimal build
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v3
-    - name: deps
+    - name: Dependencies
       run: |
-        sudo apt install libncurses5-dev libreadline-dev nettle-dev \
+        sudo apt install libncurses5-dev libreadline-dev nettle-dev libfmt-dev \
         libgnutls28-dev libcppunit-dev libmsgpack-dev libargon2-0-dev
-    - name: cmake
+    - name: Configure
       run: |
         mkdir build && cd build
         cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Debug \
                  -DOPENDHT_C=Off -DOPENDHT_TESTS=On -DOPENDHT_PEER_DISCOVERY=Off -DOPENDHT_PYTHON=Off \
                  -DOPENDHT_TOOLS=On -DOPENDHT_PROXY_SERVER=Off -DOPENDHT_PROXY_CLIENT=Off
-    - name: make
+    - name: Build
       run: cd build && make
+    - name: Unit tests
+      run: cd build && ./opendht_unit_tests
 
   build-macos:
     name: macOS/Clang build
     runs-on: macos-11
     steps:
     - uses: actions/checkout@v3
-    - name: deps
+    - name: Dependencies
       run: |
         brew install msgpack-cxx asio gnutls nettle readline fmt jsoncpp argon2 openssl http-parser cppunit
 
@@ -68,7 +76,7 @@ jobs:
         make -j8 && sudo make install
         cd ../../.. && rm -rf restinio
 
-    - name: cmake
+    - name: Configure
       run: |
         mkdir build && cd build
         export PATH="/opt/homebrew/opt/openssl@3/bin:$PATH"
@@ -79,6 +87,6 @@ jobs:
                  -DOPENDHT_C=On -DOPENDHT_TESTS=On -DOPENDHT_PEER_DISCOVERY=On -DOPENDHT_PYTHON=Off \
                  -DOPENDHT_TOOLS=On -DOPENDHT_PROXY_SERVER=On -DOPENDHT_PROXY_CLIENT=On -DOPENDHT_PUSH_NOTIFICATIONS=On
 
-    - name: make
+    - name: Build
       run: cd build && make
 
diff --git a/.github/workflows/release-package.yml b/.github/workflows/release-package.yml
index b318e4b..5a59478 100644
--- a/.github/workflows/release-package.yml
+++ b/.github/workflows/release-package.yml
@@ -12,6 +12,8 @@ env:
   IMAGE_NAME_DEPS_LLVM: ${{ github.repository }}/opendht-deps-llvm
   IMAGE_NAME_LLVM: ${{ github.repository }}/opendht-llvm
   IMAGE_NAME_DHTNODE: ${{ github.repository }}/dhtnode
+  IMAGE_NAME_ALPINE: ${{ github.repository }}/opendht-alpine
+  IMAGE_NAME_ALPINE_DEPS: ${{ github.repository }}/opendht-deps-alpine
 
 jobs:
   build-and-push-deps-image:
@@ -182,6 +184,74 @@ jobs:
           tags: ${{ steps.meta.outputs.tags }}
           labels: ${{ steps.meta.outputs.labels }}
 
+  build-and-push-image-alpine-deps:
+    name: Alpine Deps Docker image
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v3
+
+      - name: Log in to the Container registry
+        uses: docker/login-action@v2
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Extract metadata (tags, labels) for Docker
+        id: meta
+        uses: docker/metadata-action@v4
+        with:
+          images: |
+            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALPINE_DEPS }}
+
+      - name: Build and push Docker image
+        uses: docker/build-push-action@v3
+        with:
+          context: .
+          file: docker/DockerfileDepsAlpine
+          push: true
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
+
+  build-and-push-image-alpine:
+    name: Alpine Docker image
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v3
+
+      - name: Log in to the Container registry
+        uses: docker/login-action@v2
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Extract metadata (tags, labels) for Docker
+        id: meta
+        uses: docker/metadata-action@v4
+        with:
+          images: |
+            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALPINE }}
+
+      - name: Build and push Docker image
+        uses: docker/build-push-action@v3
+        with:
+          context: .
+          file: docker/DockerfileAlpine
+          push: true
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
+
   build-python-wheel:
     name: Release and build Python Wheel package
     runs-on: ubuntu-latest
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6e434bb..64dc5e9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,17 +12,17 @@ include(CMakeDependentOption)
 include(CheckIncludeFileCXX)
 include(FindPkgConfig)
 include(cmake/CheckAtomic.cmake)
+include(CTest)
 
 set (opendht_VERSION_MAJOR 2)
-set (opendht_VERSION_MINOR 4.12)
+set (opendht_VERSION_MINOR 6.0)
 set (opendht_VERSION ${opendht_VERSION_MAJOR}.${opendht_VERSION_MINOR})
 set (PACKAGE_VERSION ${opendht_VERSION})
 set (VERSION "${opendht_VERSION}")
 
 # Options
-option (OPENDHT_STATIC "Build static library" ON)
-option (OPENDHT_SHARED "Build shared library" ON)
-option (OPENDHT_LOG "Build with logs" ON)
+option (BUILD_SHARED_LIBS "Build shared library" ON)
+CMAKE_DEPENDENT_OPTION (OPENDHT_STATIC "Build static library" OFF BUILD_SHARED_LIBS ON)
 option (OPENDHT_PYTHON "Build Python bindings" OFF)
 option (OPENDHT_TOOLS "Build DHT tools" ON)
 option (OPENDHT_SYSTEMD "Install systemd module" OFF)
@@ -36,7 +36,6 @@ option (OPENDHT_PROXY_OPENSSL "Build DHT proxy with OpenSSL" ON)
 CMAKE_DEPENDENT_OPTION(OPENDHT_HTTP "Build embedded http(s) client" OFF "NOT OPENDHT_PROXY_SERVER;NOT OPENDHT_PROXY_CLIENT" ON)
 option (OPENDHT_PEER_DISCOVERY "Enable multicast peer discovery" ON)
 option (OPENDHT_INDEX "Build DHT indexation feature" OFF)
-option (OPENDHT_TESTS "Add unit tests executable" OFF)
 option (OPENDHT_TESTS_NETWORK "Enable unit tests that require network access" ON)
 option (OPENDHT_C "Build C bindings" OFF)
 
@@ -59,7 +58,16 @@ if (NOT MSVC)
     find_package (PkgConfig REQUIRED)
     pkg_search_module (GnuTLS REQUIRED IMPORTED_TARGET gnutls)
     pkg_search_module (Nettle REQUIRED IMPORTED_TARGET nettle)
-    find_package (msgpack REQUIRED NAMES msgpackc-cxx msgpack msgpack-cxx)
+    check_include_file_cxx(msgpack.hpp HAVE_MSGPACKCXX)
+    if (NOT HAVE_MSGPACKCXX)
+        find_package(msgpack QUIET CONFIG NAMES msgpack msgpackc-cxx)
+        if (NOT msgpack_FOUND)
+            find_package(msgpack REQUIRED CONFIG NAMES msgpack-cxx)
+            set(MSGPACK_TARGET "msgpack-cxx")
+        else()
+            set(MSGPACK_TARGET "msgpackc-cxx")
+        endif()
+    endif()
     if (OPENDHT_TOOLS)
         find_package (Readline 6 REQUIRED)
     endif ()
@@ -79,10 +87,10 @@ if (NOT MSVC)
         find_path(ASIO_INCLUDE_DIR asio.hpp REQUIRED)
     endif ()
 
+    find_package(fmt)
+
     if (OPENDHT_HTTP)
         find_package(Restinio REQUIRED)
-        find_library(FMT_LIBRARY fmt)
-        add_library(fmt SHARED IMPORTED)
         find_library(HTTP_PARSER_LIBRARY http_parser)
         add_library(http_parser SHARED IMPORTED)
         set(http_parser_lib "-lhttp_parser")
@@ -145,15 +153,7 @@ else ()
 endif ()
 set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMSGPACK_NO_BOOST -DMSGPACK_DISABLE_LEGACY_NIL -DMSGPACK_DISABLE_LEGACY_CONVERT")
 
-if (NOT CMAKE_BUILD_TYPE)
-    set(CMAKE_BUILD_TYPE Release)
-endif ()
 add_definitions(-DPACKAGE_VERSION="${opendht_VERSION}")
-if (OPENDHT_LOG)
-    add_definitions(-DOPENDHT_LOG=true)
-else ()
-    add_definitions(-DOPENDHT_LOG=false)
-endif()
 
 if (ASIO_INCLUDE_DIR)
     include_directories (SYSTEM "${ASIO_INCLUDE_DIR}")
@@ -172,8 +172,8 @@ include_directories (
 include (GNUInstallDirs)
 set (prefix ${CMAKE_INSTALL_PREFIX})
 set (exec_prefix "\${prefix}")
-set (libdir "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
-set (includedir "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
+set (libdir "${CMAKE_INSTALL_FULL_LIBDIR}")
+set (includedir "${CMAKE_INSTALL_FULL_INCLUDEDIR}")
 set (bindir "${CMAKE_INSTALL_FULL_BINDIR}")
 set (sysconfdir "${CMAKE_INSTALL_FULL_SYSCONFDIR}")
 set (top_srcdir "${CMAKE_CURRENT_SOURCE_DIR}")
@@ -181,7 +181,6 @@ set (top_srcdir "${CMAKE_CURRENT_SOURCE_DIR}")
 # Sources
 list (APPEND opendht_SOURCES
     src/utils.cpp
-    src/infohash.cpp
     src/crypto.cpp
     src/default_types.cpp
     src/node.cpp
@@ -227,7 +226,7 @@ list (APPEND opendht_HEADERS
     include/opendht/rate_limiter.h
     include/opendht/securedht.h
     include/opendht/log.h
-    include/opendht/log_enable.h
+    include/opendht/logger.h
     include/opendht/thread_pool.h
     include/opendht/network_utils.h
     include/opendht.h
@@ -293,28 +292,8 @@ if (MSVC)
 endif ()
 
 # Targets
-if (OPENDHT_STATIC)
-    if (NOT MSVC)
-        add_library (opendht-static STATIC
-            ${opendht_SOURCES}
-            ${opendht_HEADERS}
-        )
-        set_target_properties (opendht-static PROPERTIES OUTPUT_NAME "opendht")
-        target_include_directories(opendht-static SYSTEM PRIVATE ${argon2_INCLUDE_DIRS})
-        target_link_libraries(opendht-static
-            PRIVATE PkgConfig::argon2
-            PUBLIC ${CMAKE_THREAD_LIBS_INIT} PkgConfig::GnuTLS PkgConfig::Nettle msgpackc-cxx
-                ${FMT_LIBRARY} ${HTTP_PARSER_LIBRARY})
-        if (Jsoncpp_FOUND)
-            target_link_libraries(opendht-static PUBLIC PkgConfig::Jsoncpp)
-        endif()
-        if (OPENDHT_PROXY_OPENSSL)
-            target_link_libraries(opendht-static PUBLIC PkgConfig::OPENSSL)
-        endif()
-        if (APPLE)
-            target_link_libraries(opendht-static PRIVATE SYSTEM "-framework CoreFoundation" "-framework Security")
-        endif()
-    else ()
+if (MSVC)
+    if (OPENDHT_STATIC)
         if (OPENDHT_TOOLS)
             function (add_obj_lib name libfile)
                 add_library(${name} OBJECT IMPORTED)
@@ -353,64 +332,63 @@ if (OPENDHT_STATIC)
                 ${PROJECT_SOURCE_DIR}/../openssl/libcrypto.lib
             )
         endif ()
-        add_library (opendht-static STATIC
-            ${opendht_SOURCES}
-            ${opendht_HEADERS}
-            ${obj_libs}
-        )
-        target_link_libraries(opendht-static PUBLIC ${Win32_STATIC_LIBRARIES} ${Win32_IMPORT_LIBRARIES})
         set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /ignore:4006")
-        set_target_properties (opendht-static PROPERTIES OUTPUT_NAME "libopendht")
     endif()
-    install (TARGETS opendht-static DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht)
 endif ()
 
-if (OPENDHT_SHARED)
-    add_library (opendht SHARED
-        ${opendht_SOURCES}
-        ${opendht_HEADERS}
-    )
-    set_target_properties (opendht PROPERTIES IMPORT_SUFFIX "_import.lib")
-    set_target_properties (opendht PROPERTIES SOVERSION ${opendht_VERSION_MAJOR} VERSION ${opendht_VERSION})
-    target_compile_definitions(opendht PRIVATE OPENDHT_BUILD)
+add_library (opendht
+    ${opendht_SOURCES}
+    ${opendht_HEADERS}
+    ${obj_libs}
+)
+set_target_properties (opendht PROPERTIES OUTPUT_NAME "opendht")
+
+if (NOT HAVE_MSGPACKCXX)
+    target_link_libraries(opendht PUBLIC ${MSGPACK_TARGET})
+endif()
+if (APPLE)
+    target_link_libraries(opendht PRIVATE SYSTEM "-framework CoreFoundation" "-framework Security")
+endif()
+if (MSVC)
+    if (OPENDHT_STATIC)
+        target_link_libraries(opendht PUBLIC ${Win32_STATIC_LIBRARIES} ${Win32_IMPORT_LIBRARIES})
+        set_target_properties (opendht PROPERTIES OUTPUT_NAME "libopendht")
+    endif()
+else()
     target_link_libraries(opendht
-        PUBLIC ${CMAKE_THREAD_LIBS_INIT} msgpackc-cxx
-        PRIVATE PkgConfig::GnuTLS PkgConfig::Nettle
-                ${FMT_LIBRARY} ${HTTP_PARSER_LIBRARY} PkgConfig::argon2)
+        PRIVATE
+            PkgConfig::argon2
+            PkgConfig::Nettle
+            ${HTTP_PARSER_LIBRARY}
+        PUBLIC
+            ${CMAKE_THREAD_LIBS_INIT}
+            PkgConfig::GnuTLS
+            fmt::fmt
+    )
     if (Jsoncpp_FOUND)
         target_link_libraries(opendht PUBLIC PkgConfig::Jsoncpp)
     endif()
     if (OPENDHT_PROXY_OPENSSL)
         target_link_libraries(opendht PUBLIC PkgConfig::OPENSSL)
-    endif()    
-    if (APPLE)
-        target_link_libraries(opendht PRIVATE SYSTEM "-framework CoreFoundation" "-framework Security")
-    endif ()
+    endif()
+endif()
 
-    install (TARGETS opendht DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht)
+if (BUILD_SHARED_LIBS)
+    set_target_properties (opendht PROPERTIES IMPORT_SUFFIX "_import.lib")
+    set_target_properties (opendht PROPERTIES SOVERSION ${opendht_VERSION_MAJOR} VERSION ${opendht_VERSION})
+    target_compile_definitions(opendht PRIVATE OPENDHT_BUILD)
 endif ()
+install (TARGETS opendht DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht)
 
 if (OPENDHT_C)
-    if (OPENDHT_SHARED)
-        add_library (opendht-c SHARED
-            c/opendht.cpp
-            c/opendht_c.h
-        )
-        target_compile_definitions(opendht-c PRIVATE OPENDHT_C_BUILD)
-        target_link_libraries(opendht-c PRIVATE opendht)
-        set_target_properties (opendht-c PROPERTIES SOVERSION ${opendht_VERSION_MAJOR} VERSION ${opendht_VERSION})
-        install (TARGETS opendht-c DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht-c)
-    endif ()
-
-    if (OPENDHT_STATIC)
-        add_library (opendht-c-static STATIC
-            c/opendht.cpp
-            c/opendht_c.h
-        )
-        set_target_properties (opendht-c-static PROPERTIES OUTPUT_NAME "opendht-c")
-        target_link_libraries(opendht-c-static PRIVATE opendht-static)
-        install (TARGETS opendht-c-static DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht-c-static)
-    endif ()
+    add_library (opendht-c
+        c/opendht.cpp
+        c/opendht_c.h
+    )
+    target_compile_definitions(opendht-c PRIVATE OPENDHT_C_BUILD)
+    target_link_libraries(opendht-c PRIVATE opendht)
+    set_target_properties (opendht-c PROPERTIES SOVERSION ${opendht_VERSION_MAJOR} VERSION ${opendht_VERSION})
+    install (TARGETS opendht-c DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht-c)
 
     # PkgConfig module
     configure_file (
@@ -450,7 +428,7 @@ install (EXPORT opendht DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/opendht FILE o
 install (FILES ${CMAKE_CURRENT_BINARY_DIR}/opendhtConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/opendht)
 
 # Unit tests
-if (OPENDHT_TESTS)
+if (BUILD_TESTING AND NOT MSVC)
     pkg_search_module(Cppunit REQUIRED IMPORTED_TARGET cppunit)
     # unit testing
     list (APPEND test_FILES
@@ -485,21 +463,10 @@ if (OPENDHT_TESTS)
         tests/tests_runner.cpp
         ${test_FILES}
     )
-    target_include_directories(opendht_unit_tests SYSTEM PRIVATE ${Cppunit_INCLUDE_DIR})
-    target_link_directories(opendht_unit_tests PRIVATE ${Cppunit_LIBRARY_DIRS})
-    if (OPENDHT_SHARED)
-        target_link_libraries(opendht_unit_tests opendht)
-    else ()
-        target_link_libraries(opendht_unit_tests opendht-static)
-    endif ()
-    target_link_libraries(opendht_unit_tests
+    target_link_libraries(opendht_unit_tests PRIVATE
+       opendht
        ${CMAKE_THREAD_LIBS_INIT}
        PkgConfig::Cppunit
-       ${GNUTLS_LIBRARIES}
     )
-    if (OPENDHT_PROXY_OPENSSL)
-        target_link_libraries(opendht_unit_tests PkgConfig::OPENSSL)
-    endif()
-    enable_testing()
     add_test(TEST opendht_unit_tests)
 endif()
diff --git a/README.md b/README.md
index ee5377d..b4d5915 100644
--- a/README.md
+++ b/README.md
@@ -25,11 +25,6 @@ See the wiki: <https://github.com/savoirfairelinux/opendht/wiki>
 
 Build instructions: <https://github.com/savoirfairelinux/opendht/wiki/Build-the-library>
 
-#### How-to build a simple client app
-```bash
-g++ main.cpp -std=c++17 -lopendht
-```
-
 ## Examples
 ### C++ example
 The `tools` directory includes simple example programs :
@@ -117,6 +112,3 @@ See COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html for the full GPLv3 l
 ## Acknowledgements
 This project was originally based on https://github.com/jech/dht by Juliusz Chroboczek.
 It is independent from another project called OpenDHT (Sean Rhea. Ph.D. Thesis, 2005), now extinct.
-
-## Donations
-We gratefully accept Bitcoin donations to support OpenDHT development at: `bitcoin:3EykSd1An888efq4Bq3KaV3hJ3JQ4FPnwm`.
diff --git a/c/opendht.cpp b/c/opendht.cpp
index 7ba15ea..d1fe081 100644
--- a/c/opendht.cpp
+++ b/c/opendht.cpp
@@ -261,7 +261,7 @@ dht_pkid dht_certificate_get_long_id(const dht_certificate* c) {
 
 dht_publickey* dht_certificate_get_publickey(const dht_certificate* c) {
     const auto& cert = *reinterpret_cast<const CertSp*>(c);
-    return reinterpret_cast<dht_publickey*>(new PubkeySp(std::make_shared<dht::crypto::PublicKey>(cert->getPublicKey())));
+    return reinterpret_cast<dht_publickey*>(new PubkeySp(cert->getSharedPublicKey()));
 }
 
 // dht::crypto::Identity
diff --git a/configure.ac b/configure.ac
index 74e5c76..e75442a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
 dnl define macros
 m4_define([opendht_major_version], 2)
-m4_define([opendht_minor_version], 4)
-m4_define([opendht_patch_version], 12)
+m4_define([opendht_minor_version], 6)
+m4_define([opendht_patch_version], 0)
 m4_define([opendht_version],
 		  [opendht_major_version.opendht_minor_version.opendht_patch_version])
 
@@ -34,12 +34,6 @@ AC_LANG(C++)
 AC_PROG_CXX
 AX_CXX_COMPILE_STDCXX(17,[noext],[mandatory])
 
-dnl Check for logs
-AC_ARG_ENABLE([logs], [AS_HELP_STRING([--disable-logs], [Disable DHT logs])])
-AS_IF([test "x$enable_logs" != "xno"], [], [
-	AC_DEFINE([OPENDHT_LOG], [false], [Define if DHT logs are disabled])
-])
-
 dnl Check for C binding
 AC_ARG_ENABLE([c], [AS_HELP_STRING([--disable-c], [Disable DHT C binding])])
 AM_CONDITIONAL(ENABLE_C, test x$enable_c != "xno")
@@ -171,15 +165,16 @@ AS_IF([test "x$have_openssl" = "xyes"], [
     AC_MSG_NOTICE([Not using OpenSSL])
 ])
 
+PKG_CHECK_MODULES([Fmt], [fmt >= 5.3.0], [have_fmt=yes], [have_fmt=no])
+AS_IF([test "x$have_fmt" = "xyes"], [
+    AC_MSG_NOTICE([Using libfmt])
+], [
+    AC_MSG_NOTICE([Missing libfmt files])
+])
+
 AM_COND_IF([PROXY_CLIENT_OR_SERVER], [
     AC_CHECK_HEADERS([asio.hpp],, AC_MSG_ERROR([Missing Asio headers files]))
     CXXFLAGS="${CXXFLAGS} -DASIO_STANDALONE"
-    PKG_CHECK_MODULES([Fmt], [fmt >= 5.3.0], [have_fmt=yes], [have_fmt=no])
-    AS_IF([test "x$have_fmt" = "xyes"], [
-        AC_MSG_NOTICE([Using libfmt])
-    ], [
-        AC_MSG_NOTICE([Missing libfmt files])
-    ])
     # http_parser has no pkgconfig, instead we check with:
     AC_CHECK_LIB(http_parser, exit,, AC_MSG_ERROR([Missing HttpParser library files]))
     AC_CHECK_HEADERS([http_parser.h], [http_parser_headers=yes; break;])
diff --git a/debian/changelog b/debian/changelog
index e526844..a0436b9 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+opendht (2.6.0rc1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Wed, 24 May 2023 17:05:09 -0000
+
 opendht (2.4.12-7) unstable; urgency=medium
 
   [ Petter Reinholdtsen ]
diff --git a/debian/patches/1000-cmake-fix-no-atomic-64.patch b/debian/patches/1000-cmake-fix-no-atomic-64.patch
index c5c8b96..f72e671 100644
--- a/debian/patches/1000-cmake-fix-no-atomic-64.patch
+++ b/debian/patches/1000-cmake-fix-no-atomic-64.patch
@@ -13,11 +13,11 @@ are two such architectures.
  CMakeLists.txt | 4 +++-
  1 file changed, 3 insertions(+), 1 deletion(-)
 
-diff --git a/CMakeLists.txt b/CMakeLists.txt
-index 6e434bb5..3869af4d 100644
---- a/CMakeLists.txt
-+++ b/CMakeLists.txt
-@@ -48,7 +48,9 @@ set (CMAKE_CXX_STANDARD 17)
+Index: opendht.git/CMakeLists.txt
+===================================================================
+--- opendht.git.orig/CMakeLists.txt
++++ opendht.git/CMakeLists.txt
+@@ -47,7 +47,9 @@ set (CMAKE_CXX_STANDARD 17)
  set (CMAKE_CXX_STANDARD_REQUIRED on)
  
  # Dependencies
@@ -28,6 +28,3 @@ index 6e434bb5..3869af4d 100644
      link_libraries (atomic)
  endif ()
  
--- 
-2.39.1
-
diff --git a/debian/patches/1010-man-page-formatting.patch b/debian/patches/1010-man-page-formatting.patch
index 7023db3..b38fa19 100644
--- a/debian/patches/1010-man-page-formatting.patch
+++ b/debian/patches/1010-man-page-formatting.patch
@@ -7,8 +7,10 @@ Forwarded: no
 Reviewed-By: Petter Reinholdtsen
 Last-Update: 2023-02-22
 
---- opendht-2.4.12.orig/doc/dhtnode.1
-+++ opendht-2.4.12/doc/dhtnode.1
+Index: opendht.git/doc/dhtnode.1
+===================================================================
+--- opendht.git.orig/doc/dhtnode.1
++++ opendht.git/doc/dhtnode.1
 @@ -19,7 +19,7 @@
  .SH DESCRIPTION
  Runs an OpenDHT node, with a CLI (default) or as a daemon (with \fB'-d'\fP or \fB'-s'\fP).
diff --git a/debian/patches/2000-cmake-python-no-build-install.patch b/debian/patches/2000-cmake-python-no-build-install.patch
index 28fe1b1..4e0d86d 100644
--- a/debian/patches/2000-cmake-python-no-build-install.patch
+++ b/debian/patches/2000-cmake-python-no-build-install.patch
@@ -5,11 +5,11 @@ Author: Amin Bandali <bandali@gnu.org>
 Forwarded: no
 Last-Update: 2023-01-29
 
-diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
-index 595cded..cd74c18 100644
---- a/python/CMakeLists.txt
-+++ b/python/CMakeLists.txt
-@@ -5,15 +5,15 @@ set(CURRENT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
+Index: opendht.git/python/CMakeLists.txt
+===================================================================
+--- opendht.git.orig/python/CMakeLists.txt
++++ opendht.git/python/CMakeLists.txt
+@@ -5,15 +5,15 @@ set(CURRENT_BINARY_DIR ${CMAKE_CURRENT_B
  configure_file(setup.py.in setup.py)
  configure_file(pyproject.toml pyproject.toml COPYONLY)
  
diff --git a/docker/Dockerfile b/docker/Dockerfile
index b1d36c8..f2e70ef 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -2,8 +2,9 @@ FROM ghcr.io/savoirfairelinux/opendht/opendht-deps:latest
 LABEL maintainer="Adrien Béraud <adrien.beraud@savoirfairelinux.com>"
 LABEL org.opencontainers.image.source https://github.com/savoirfairelinux/opendht
 
-RUN git clone https://github.com/savoirfairelinux/opendht.git \
-	&& cd opendht && mkdir build && cd build \
+COPY . opendht
+
+RUN cd opendht && mkdir build && cd build \
 	&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr \
 				-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On \
 				-DOPENDHT_C=On \
diff --git a/docker/DockerfileAlpine b/docker/DockerfileAlpine
new file mode 100644
index 0000000..e6f865e
--- /dev/null
+++ b/docker/DockerfileAlpine
@@ -0,0 +1,37 @@
+FROM ghcr.io/savoirfairelinux/opendht/opendht-deps-alpine:latest AS build
+LABEL maintainer="Adrien Béraud <adrien.beraud@savoirfairelinux.com>"
+LABEL org.opencontainers.image.source https://github.com/savoirfairelinux/opendht
+
+COPY . opendht
+
+RUN mkdir /install
+ENV DESTDIR /install
+
+RUN cd opendht && mkdir build && cd build \
+	&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr \
+				-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On \
+				-DOPENDHT_C=On \
+				-DOPENDHT_PEER_DISCOVERY=On \
+				-DOPENDHT_PYTHON=On \
+				-DOPENDHT_TOOLS=On \
+				-DOPENDHT_PROXY_SERVER=On \
+				-DOPENDHT_PROXY_CLIENT=On \
+				-DOPENDHT_SYSTEMD=On \
+	&& make -j8 && make install
+
+FROM alpine:3.17 AS install
+COPY --from=build /install /
+RUN apk add --no-cache \
+        libstdc++ \
+        gnutls \
+        nettle \
+        openssl \
+        argon2-dev \
+        jsoncpp \
+        fmt \
+        http-parser \
+        readline \
+        ncurses
+CMD ["dhtnode", "-b", "bootstrap.jami.net", "-p", "4222", "--proxyserver", "8080"]
+EXPOSE 4222/udp
+EXPOSE 8080/tcp
diff --git a/docker/DockerfileBionic b/docker/DockerfileBionic
index 5772912..d1acb91 100644
--- a/docker/DockerfileBionic
+++ b/docker/DockerfileBionic
@@ -2,8 +2,9 @@ FROM ghcr.io/savoirfairelinux/opendht/opendht-deps-bionic:latest
 LABEL maintainer="Adrien Béraud <adrien.beraud@savoirfairelinux.com>"
 LABEL org.opencontainers.image.source https://github.com/savoirfairelinux/opendht
 
-RUN git clone https://github.com/savoirfairelinux/opendht.git \
-	&& cd opendht && mkdir build && cd build \
+COPY . opendht
+
+RUN cd opendht && mkdir build && cd build \
 	&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr \
 				-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On \
 				-DOPENDHT_C=On \
diff --git a/docker/DockerfileDepsAlpine b/docker/DockerfileDepsAlpine
new file mode 100644
index 0000000..557942e
--- /dev/null
+++ b/docker/DockerfileDepsAlpine
@@ -0,0 +1,23 @@
+FROM alpine:3.17
+LABEL maintainer="Adrien Béraud <adrien.beraud@savoirfairelinux.com>"
+LABEL org.opencontainers.image.source https://github.com/savoirfairelinux/opendht
+
+RUN apk add --no-cache \
+        build-base cmake ninja git wget \
+        cython python3-dev py3-setuptools \
+        ncurses-dev readline-dev nettle-dev \
+        cppunit-dev gnutls-dev jsoncpp-dev \
+        argon2-dev openssl-dev fmt-dev \
+        http-parser-dev asio-dev msgpack-cxx-dev \
+    && rm -rf /var/cache/apk/*
+
+RUN echo "*** Downloading RESTinio ***" \
+    && mkdir restinio && cd restinio \
+    && wget https://github.com/aberaud/restinio/archive/bbaa034dbcc7555ce67df0f8a1475591a7441733.tar.gz \
+    && tar -xzf bbaa034dbcc7555ce67df0f8a1475591a7441733.tar.gz \
+    && cd restinio-bbaa034dbcc7555ce67df0f8a1475591a7441733/dev \
+    && cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=OFF -DRESTINIO_SAMPLE=OFF \
+             -DRESTINIO_INSTALL_SAMPLES=OFF -DRESTINIO_BENCH=OFF -DRESTINIO_INSTALL_BENCHES=OFF \
+             -DRESTINIO_FIND_DEPS=ON -DRESTINIO_ALLOW_SOBJECTIZER=Off -DRESTINIO_USE_BOOST_ASIO=none . \
+    && make -j8 && make install \
+    && cd ../../.. && rm -rf restinio
diff --git a/docker/DockerfileFocal b/docker/DockerfileFocal
index 5772912..d1acb91 100644
--- a/docker/DockerfileFocal
+++ b/docker/DockerfileFocal
@@ -2,8 +2,9 @@ FROM ghcr.io/savoirfairelinux/opendht/opendht-deps-bionic:latest
 LABEL maintainer="Adrien Béraud <adrien.beraud@savoirfairelinux.com>"
 LABEL org.opencontainers.image.source https://github.com/savoirfairelinux/opendht
 
-RUN git clone https://github.com/savoirfairelinux/opendht.git \
-	&& cd opendht && mkdir build && cd build \
+COPY . opendht
+
+RUN cd opendht && mkdir build && cd build \
 	&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr \
 				-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On \
 				-DOPENDHT_C=On \
diff --git a/docker/DockerfileLlvm b/docker/DockerfileLlvm
index 339fddf..f6fe1e3 100644
--- a/docker/DockerfileLlvm
+++ b/docker/DockerfileLlvm
@@ -2,8 +2,9 @@ FROM ghcr.io/savoirfairelinux/opendht/opendht-deps-llvm:latest
 LABEL maintainer="Adrien Béraud <adrien.beraud@savoirfairelinux.com>"
 LABEL org.opencontainers.image.source https://github.com/savoirfairelinux/opendht
 
-RUN git clone https://github.com/savoirfairelinux/opendht.git \
-	&& cd opendht && mkdir build && cd build \
+COPY . opendht
+
+RUN cd opendht && mkdir build && cd build \
 	&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr \
 				-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On \
 				-DOPENDHT_C=On \
diff --git a/include/opendht/crypto.h b/include/opendht/crypto.h
index f26a65f..84d5df0 100644
--- a/include/opendht/crypto.h
+++ b/include/opendht/crypto.h
@@ -390,7 +390,11 @@ struct OPENDHT_PUBLIC Certificate {
      */
     Certificate(gnutls_x509_crt_t crt) noexcept : cert(crt) {}
 
-    Certificate(Certificate&& o) noexcept : cert(o.cert), issuer(std::move(o.issuer)) { o.cert = nullptr; };
+    Certificate(Certificate&& o) noexcept
+        : cert(o.cert)
+        , issuer(std::move(o.issuer))
+        , publicKey_(std::move(o.publicKey_))
+        { o.cert = nullptr; };
 
     /**
      * Import certificate (PEM or DER) or certificate chain (PEM),
@@ -498,7 +502,8 @@ struct OPENDHT_PUBLIC Certificate {
     void msgpack_unpack(const msgpack::object& o);
 
     explicit operator bool() const { return cert; }
-    PublicKey getPublicKey() const;
+    const PublicKey& getPublicKey() const;
+    const std::shared_ptr<PublicKey>& getSharedPublicKey() const;
 
     /** Same as getPublicKey().getId() */
     const InfoHash& getId() const;
@@ -631,6 +636,9 @@ private:
     };
 
     std::set<std::shared_ptr<RevocationList>, crlNumberCmp> revocation_lists;
+
+    mutable std::mutex publicKeyMutex_ {};
+    mutable std::shared_ptr<PublicKey> publicKey_ {};
 };
 
 struct OPENDHT_PUBLIC TrustList
diff --git a/include/opendht/dht.h b/include/opendht/dht.h
index f940cc5..3359571 100644
--- a/include/opendht/dht.h
+++ b/include/opendht/dht.h
@@ -128,7 +128,7 @@ public:
      */
     void insertNode(const InfoHash& id, const SockAddr&) override;
     void insertNode(const NodeExport& n) override {
-        insertNode(n.id, SockAddr(n.ss, n.sslen));
+        insertNode(n.id, n.addr);
     }
 
     void pingNode(SockAddr, DoneCallbackSimple&& cb={}) override;
diff --git a/include/opendht/dht_interface.h b/include/opendht/dht_interface.h
index e621b16..25faaa8 100644
--- a/include/opendht/dht_interface.h
+++ b/include/opendht/dht_interface.h
@@ -19,7 +19,8 @@
 #pragma once
 
 #include "infohash.h"
-#include "log_enable.h"
+#include "logger.h"
+#include "node_export.h"
 
 #include <queue>
 
@@ -231,18 +232,6 @@ public:
 
     virtual std::vector<SockAddr> getPublicAddress(sa_family_t family = 0) = 0;
 
-    /**
-     * Enable or disable logging of DHT internal messages
-     */
-    virtual void setLoggers(LogMethod error = {}, LogMethod warn = {}, LogMethod debug = {}) {
-        if (logger_) {
-            logger_->DBG = std::move(debug);
-            logger_->WARN = std::move(warn);
-            logger_->ERR = std::move(error);
-        } else
-            logger_= std::make_shared<Logger>(std::move(error), std::move(warn), std::move(debug));
-    }
-
     virtual void setLogger(const Logger& l) {
         if (logger_)
             *logger_ = l;
diff --git a/include/opendht/dht_proxy_server.h b/include/opendht/dht_proxy_server.h
index 440d82b..b68e42e 100644
--- a/include/opendht/dht_proxy_server.h
+++ b/include/opendht/dht_proxy_server.h
@@ -87,7 +87,7 @@ public:
      */
     DhtProxyServer(const std::shared_ptr<DhtRunner>& dht,
         const ProxyServerConfig& config = {},
-        const std::shared_ptr<dht::Logger>& logger = {});
+        const std::shared_ptr<log::Logger>& logger = {});
 
     virtual ~DhtProxyServer();
 
@@ -278,6 +278,9 @@ private:
 #ifdef OPENDHT_PUSH_NOTIFICATIONS
     PushType getTypeFromString(const std::string& type);
     std::string getDefaultTopic(PushType type);
+
+    RequestStatus pingPush(restinio::request_handle_t request,
+                         restinio::router::route_params_t /*params*/);
     /**
      * Subscribe to push notifications for an iOS or Android device.
      * Method: SUBSCRIBE "/{InfoHash: .*}"
@@ -361,7 +364,7 @@ private:
     mutable std::mutex requestLock_;
     std::map<unsigned int /*id*/, std::shared_ptr<http::Request>> requests_;
 
-    std::shared_ptr<dht::Logger> logger_;
+    std::shared_ptr<log::Logger> logger_;
 
     std::shared_ptr<ServerStats> stats_;
     std::shared_ptr<NodeInfo> nodeInfo_ {};
diff --git a/include/opendht/dhtrunner.h b/include/opendht/dhtrunner.h
index 450b315..948cef3 100644
--- a/include/opendht/dhtrunner.h
+++ b/include/opendht/dhtrunner.h
@@ -25,8 +25,9 @@
 #include "value.h"
 #include "callbacks.h"
 #include "sockaddr.h"
-#include "log_enable.h"
+#include "logger.h"
 #include "network_utils.h"
+#include "node_export.h"
 
 #include <thread>
 #include <mutex>
@@ -348,7 +349,6 @@ public:
     void setLogger(const Logger& logger) {
         setLogger(std::make_shared<Logger>(logger));
     }
-    void setLoggers(LogMethod err = {}, LogMethod warn = {}, LogMethod debug = {});
 
     /**
      * Only print logs related to the given InfoHash (if given), or disable filter (if zeroes).
diff --git a/include/opendht/http.h b/include/opendht/http.h
index 6d4ffba..b4ddee4 100644
--- a/include/opendht/http.h
+++ b/include/opendht/http.h
@@ -53,7 +53,9 @@ class tls_socket_t;
 }
 
 namespace dht {
+namespace log {
 struct Logger;
+}
 
 namespace crypto {
 struct Certificate;
@@ -88,9 +90,9 @@ public:
 class OPENDHT_PUBLIC Connection : public std::enable_shared_from_this<Connection>
 {
 public:
-    Connection(asio::io_context& ctx, const bool ssl = true, std::shared_ptr<dht::Logger> l = {});
+    Connection(asio::io_context& ctx, const bool ssl = true, std::shared_ptr<log::Logger> l = {});
     Connection(asio::io_context& ctx, std::shared_ptr<dht::crypto::Certificate> server_ca,
-               const dht::crypto::Identity& identity, std::shared_ptr<dht::Logger> l = {});
+               const dht::crypto::Identity& identity, std::shared_ptr<log::Logger> l = {});
     ~Connection();
 
     inline unsigned int id() const { return  id_; };
@@ -148,7 +150,7 @@ private:
     asio::ip::address local_address_;
 
     std::unique_ptr<asio::steady_timer> timeout_timer_;
-    std::shared_ptr<dht::Logger> logger_;
+    std::shared_ptr<log::Logger> logger_;
     bool checkOcsp_ {false};
 };
 
@@ -172,15 +174,15 @@ public:
     using ResolverCb = std::function<void(const asio::error_code& ec,
                                           const std::vector<asio::ip::tcp::endpoint>& endpoints)>;
 
-    Resolver(asio::io_context& ctx, const std::string& url, std::shared_ptr<dht::Logger> logger = {});
+    Resolver(asio::io_context& ctx, const std::string& url, std::shared_ptr<log::Logger> logger = {});
     Resolver(asio::io_context& ctx, const std::string& host, const std::string& service,
-             const bool ssl = false, std::shared_ptr<dht::Logger> logger = {});
+             const bool ssl = false, std::shared_ptr<log::Logger> logger = {});
 
     // use already resolved endpoints with classes using this resolver
     Resolver(asio::io_context& ctx, std::vector<asio::ip::tcp::endpoint> endpoints,
-             const bool ssl = false, std::shared_ptr<dht::Logger> logger = {});
+             const bool ssl = false, std::shared_ptr<log::Logger> logger = {});
     Resolver(asio::io_context& ctx, const std::string& url, std::vector<asio::ip::tcp::endpoint> endpoints,
-            std::shared_ptr<dht::Logger> logger = {});
+            std::shared_ptr<log::Logger> logger = {});
 
     ~Resolver();
 
@@ -190,7 +192,7 @@ public:
 
     void add_callback(ResolverCb cb, sa_family_t family = AF_UNSPEC);
 
-    std::shared_ptr<Logger> getLogger() const {
+    std::shared_ptr<log::Logger> getLogger() const {
         return logger_;
     }
 
@@ -208,7 +210,7 @@ private:
     bool completed_ {false};
     std::queue<ResolverCb> cbs_;
 
-    std::shared_ptr<dht::Logger> logger_;
+    std::shared_ptr<log::Logger> logger_;
 };
 
 class Request;
@@ -240,14 +242,14 @@ public:
 
     // resolves implicitly
     Request(asio::io_context& ctx, const std::string& url, const Json::Value& json, OnJsonCb jsoncb,
-            std::shared_ptr<dht::Logger> logger = {});
+            std::shared_ptr<log::Logger> logger = {});
     Request(asio::io_context& ctx, const std::string& url, OnJsonCb jsoncb,
-            std::shared_ptr<dht::Logger> logger = {});
+            std::shared_ptr<log::Logger> logger = {});
 
-    Request(asio::io_context& ctx, const std::string& url, std::shared_ptr<dht::Logger> logger = {});
+    Request(asio::io_context& ctx, const std::string& url, std::shared_ptr<log::Logger> logger = {});
     Request(asio::io_context& ctx, const std::string& host, const std::string& service,
-            const bool ssl = false, std::shared_ptr<dht::Logger> logger = {});
-    Request(asio::io_context& ctx, const std::string& url, OnDoneCb onDone, std::shared_ptr<dht::Logger> logger = {});
+            const bool ssl = false, std::shared_ptr<log::Logger> logger = {});
+    Request(asio::io_context& ctx, const std::string& url, OnDoneCb onDone, std::shared_ptr<log::Logger> logger = {});
 
     // user defined resolver
     Request(asio::io_context& ctx, std::shared_ptr<Resolver> resolver, sa_family_t family = AF_UNSPEC);
@@ -255,7 +257,7 @@ public:
 
     // user defined resolved endpoints
     Request(asio::io_context& ctx, std::vector<asio::ip::tcp::endpoint>&& endpoints,
-            const bool ssl = false, std::shared_ptr<dht::Logger> logger = {});
+            const bool ssl = false, std::shared_ptr<log::Logger> logger = {});
 
     ~Request();
 
@@ -282,7 +284,7 @@ public:
 
     void set_certificate_authority(std::shared_ptr<dht::crypto::Certificate> certificate);
     void set_identity(const dht::crypto::Identity& identity);
-    void set_logger(std::shared_ptr<dht::Logger> logger);
+    void set_logger(std::shared_ptr<log::Logger> logger);
 
     /**
      * Define the HTTP header/body as per https://tools.ietf.org/html/rfc7230.
@@ -347,7 +349,7 @@ private:
 
     mutable std::mutex mutex_;
 
-    std::shared_ptr<dht::Logger> logger_;
+    std::shared_ptr<log::Logger> logger_;
 
     restinio::http_request_header_t header_;
     std::map<restinio::http_field_t, std::string> headers_;
diff --git a/include/opendht/infohash.h b/include/opendht/infohash.h
index 7ec7292..7de9336 100644
--- a/include/opendht/infohash.h
+++ b/include/opendht/infohash.h
@@ -22,6 +22,7 @@
 #include "rng.h"
 
 #include <msgpack.hpp>
+#include <fmt/core.h>
 
 #ifndef _WIN32
 #include <netinet/in.h>
@@ -44,6 +45,7 @@ typedef uint16_t in_port_t;
 #include <algorithm>
 #include <stdexcept>
 #include <sstream>
+
 #include <cstring>
 #include <cstddef>
 
@@ -228,6 +230,11 @@ public:
         return get(data.data(), data.size());
     }
 
+    template <size_t H>
+    static Hash get(const Hash<H>& o) {
+        return get(o.data(), o.size());
+    }
+
     /**
      * Computes the hash from a given data buffer of size data_len.
      */
@@ -387,25 +394,11 @@ Hash<N>::toString() const
     return std::string(to_c_str(), N*2);
 }
 
-struct OPENDHT_PUBLIC NodeExport {
-    InfoHash id;
-    sockaddr_storage ss;
-    socklen_t sslen;
-
-    template <typename Packer>
-    void msgpack_pack(Packer& pk) const
-    {
-        pk.pack_map(2);
-        pk.pack(std::string("id"));
-        pk.pack(id);
-        pk.pack(std::string("addr"));
-        pk.pack_bin(sslen);
-        pk.pack_bin_body((char*)&ss, sslen);
-    }
-
-    void msgpack_unpack(msgpack::object o);
+}
 
-    OPENDHT_PUBLIC friend std::ostream& operator<< (std::ostream& s, const NodeExport& h);
+template <size_t N>
+struct fmt::formatter<dht::Hash<N>>: formatter<string_view> {
+  constexpr auto format(const dht::Hash<N>& c, format_context& ctx) const {
+    return formatter<string_view>::format(c.to_view(), ctx);
+  }
 };
-
-}
diff --git a/include/opendht/log.h b/include/opendht/log.h
index 7369d6d..170190b 100644
--- a/include/opendht/log.h
+++ b/include/opendht/log.h
@@ -20,7 +20,7 @@
 #pragma once
 
 #include "def.h"
-#include "log_enable.h"
+#include "logger.h"
 
 #include <iostream>
 #include <memory>
@@ -34,42 +34,6 @@ class DhtRunner;
  */
 namespace log {
 
-/**
- * Terminal colors for logging
- */
-namespace Color {
-    enum Code {
-        FG_RED      = 31,
-        FG_GREEN    = 32,
-        FG_YELLOW   = 33,
-        FG_BLUE     = 34,
-        FG_DEFAULT  = 39,
-        BG_RED      = 41,
-        BG_GREEN    = 42,
-        BG_BLUE     = 44,
-        BG_DEFAULT  = 49
-    };
-    class Modifier {
-        const Code code;
-    public:
-        constexpr Modifier(Code pCode) : code(pCode) {}
-        friend std::ostream&
-        operator<<(std::ostream& os, const Modifier& mod) {
-            return os << "\033[" << mod.code << 'm';
-        }
-    };
-}
-
-constexpr const Color::Modifier def(Color::FG_DEFAULT);
-constexpr const Color::Modifier red(Color::FG_RED);
-constexpr const Color::Modifier yellow(Color::FG_YELLOW);
-
-/**
- * Print va_list to std::ostream (used for logging).
- */
-OPENDHT_PUBLIC void
-printLog(std::ostream &s, char const *m, va_list args);
-
 OPENDHT_PUBLIC
 std::shared_ptr<Logger> getStdLogger();
 
diff --git a/include/opendht/log_enable.h b/include/opendht/log_enable.h
deleted file mode 100644
index d60c947..0000000
--- a/include/opendht/log_enable.h
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- *  Copyright (C) 2014-2022 Savoir-faire Linux Inc.
- *  Author : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "infohash.h"
-
-#include <cstdarg>
-
-#ifndef OPENDHT_LOG
-#define OPENDHT_LOG true
-#endif
-
-namespace dht {
-
-// Logging related utility functions
-
-/**
- * Wrapper for logging methods
- */
-struct LogMethod {
-    LogMethod() = default;
-
-    LogMethod(LogMethod&& l) : func(std::move(l.func)) {}
-    LogMethod(const LogMethod& l) : func(l.func) {}
-
-    LogMethod& operator=(dht::LogMethod&& l) {
-        func = std::forward<LogMethod>(l.func);
-        return *this;
-    }
-    LogMethod& operator=(const dht::LogMethod& l) {
-        func = l.func;
-        return *this;
-    }
-
-    template<typename T>
-    explicit LogMethod(T&& t) : func(std::forward<T>(t)) {}
-
-    template<typename T>
-    LogMethod(const T& t) : func(t) {}
-
-    void operator()(char const* format, ...) const {
-        va_list args;
-        va_start(args, format);
-        func(format, args);
-        va_end(args);
-    }
-    inline void log(char const* format, va_list args) const {
-        func(format, args);
-    }
-    explicit operator bool() const {
-        return (bool)func;
-    }
-
-    void logPrintable(const uint8_t *buf, size_t buflen) const {
-        std::string buf_clean(buflen, '\0');
-        for (size_t i=0; i<buflen; i++)
-            buf_clean[i] = isprint(buf[i]) ? buf[i] : '.';
-        (*this)("%s", buf_clean.c_str());
-    }
-private:
-    std::function<void(char const*, va_list)> func;
-};
-
-struct Logger {
-    LogMethod ERR = {};
-    LogMethod WARN = {};
-    LogMethod DBG = {};
-
-    Logger() = default;
-    Logger(LogMethod&& err, LogMethod&& warn, LogMethod&& dbg)
-        : ERR(std::move(err)), WARN(std::move(warn)), DBG(std::move(dbg)) {}
-    void setFilter(const InfoHash& f) {
-        filter_ = f;
-        filterEnable_ = static_cast<bool>(filter_);
-    }
-    inline void log0(const LogMethod& logger, char const* format, va_list args) const {
-#if OPENDHT_LOG
-        if (logger and not filterEnable_)
-            logger.log(format, args);
-#endif
-    }
-    inline void log1(const LogMethod& logger, const InfoHash& f, char const* format, va_list args) const {
-#if OPENDHT_LOG
-        if (logger and (not filterEnable_ or f == filter_))
-            logger.log(format, args);
-#endif
-    }
-    inline void log2(const LogMethod& logger, const InfoHash& f1, const InfoHash& f2, char const* format, va_list args) const {
-#if OPENDHT_LOG
-        if (logger and (not filterEnable_ or f1 == filter_ or f2 == filter_))
-            logger.log(format, args);
-#endif
-    }
-    inline void d(char const* format, ...) const {
-#if OPENDHT_LOG
-        va_list args;
-        va_start(args, format);
-        log0(DBG, format, args);
-        va_end(args);
-#endif
-    }
-    inline void d(const InfoHash& f, char const* format, ...) const {
-#if OPENDHT_LOG
-        va_list args;
-        va_start(args, format);
-        log1(DBG, f, format, args);
-        va_end(args);
-#endif
-    }
-    inline void d(const InfoHash& f1, const InfoHash& f2, char const* format, ...) const {
-#if OPENDHT_LOG
-        va_list args;
-        va_start(args, format);
-        log2(DBG, f1, f2, format, args);
-        va_end(args);
-#endif
-    }
-    inline void w(char const* format, ...) const {
-#if OPENDHT_LOG
-        va_list args;
-        va_start(args, format);
-        log0(WARN, format, args);
-        va_end(args);
-#endif
-    }
-    inline void w(const InfoHash& f, char const* format, ...) const {
-#if OPENDHT_LOG
-        va_list args;
-        va_start(args, format);
-        log1(WARN, f, format, args);
-        va_end(args);
-#endif
-    }
-    inline void w(const InfoHash& f1, const InfoHash& f2, char const* format, ...) const {
-#if OPENDHT_LOG
-        va_list args;
-        va_start(args, format);
-        log2(WARN, f1, f2, format, args);
-        va_end(args);
-#endif
-    }
-    inline void e(char const* format, ...) const {
-#if OPENDHT_LOG
-        va_list args;
-        va_start(args, format);
-        log0(ERR, format, args);
-        va_end(args);
-#endif
-    }
-    inline void e(const InfoHash& f, char const* format, ...) const {
-#if OPENDHT_LOG
-        va_list args;
-        va_start(args, format);
-        log1(ERR, f, format, args);
-        va_end(args);
-#endif
-    }
-    inline void e(const InfoHash& f1, const InfoHash& f2, char const* format, ...) const {
-#if OPENDHT_LOG
-        va_list args;
-        va_start(args, format);
-        log2(ERR, f1, f2, format, args);
-        va_end(args);
-#endif
-    }
-private:
-    bool filterEnable_ {false};
-    InfoHash filter_ {};
-};
-
-}
diff --git a/include/opendht/logger.h b/include/opendht/logger.h
new file mode 100644
index 0000000..09af78c
--- /dev/null
+++ b/include/opendht/logger.h
@@ -0,0 +1,121 @@
+/*
+ *  Copyright (C) 2014-2022 Savoir-faire Linux Inc.
+ *  Author : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "infohash.h"
+
+#include <fmt/format.h>
+#include <fmt/printf.h>
+
+#include <functional>
+#include <string_view>
+#include <cstdarg>
+
+namespace dht {
+namespace log {
+
+enum class LogLevel {
+    debug, warning, error
+};
+
+using LogMethodFmt = std::function<void(LogLevel, fmt::string_view, fmt::format_args)>;
+using LogMethodPrintf = std::function<void(LogLevel, fmt::string_view, fmt::printf_args)>;
+
+struct Logger {
+    LogMethodFmt logger = {};
+    LogMethodPrintf loggerf = {};
+
+    Logger() = default;
+    Logger(LogMethodFmt&& logger, LogMethodPrintf&& loggerf)
+        : logger(std::move(logger)), loggerf(std::move(loggerf)) {
+            if (!logger or !loggerf)
+                throw std::invalid_argument{"logger and loggerf must be set"};
+        }
+    void setFilter(const InfoHash& f) {
+        filter_ = f;
+        filterEnable_ = static_cast<bool>(filter_);
+    }
+    inline void log0(LogLevel level, fmt::string_view format, fmt::printf_args args) const {
+        if (not filterEnable_)
+            loggerf(level, format, args);
+    }
+    inline void log1(LogLevel level, const InfoHash& f, fmt::string_view format, fmt::printf_args args) const {
+        if (not filterEnable_ or f == filter_)
+            loggerf(level, format, args);
+    }
+    inline void log2(LogLevel level, const InfoHash& f1, const InfoHash& f2, fmt::string_view format, fmt::printf_args args) const {
+        if (not filterEnable_ or f1 == filter_ or f2 == filter_)
+            loggerf(level, format, args);
+    }
+    template<typename S, typename... Args>
+    inline void debug(S&& format, Args&&... args) const {
+        logger(LogLevel::debug, std::forward<S>(format), fmt::make_format_args(args...));
+    }
+    template<typename S, typename... Args>
+    inline void warn(S&& format, Args&&... args) const {
+        logger(LogLevel::warning, std::forward<S>(format), fmt::make_format_args(args...));
+    }
+    template<typename S, typename... Args>
+    inline void error(S&& format, Args&&... args) const {
+        logger(LogLevel::error, std::forward<S>(format), fmt::make_format_args(args...));
+    }
+    template <typename... T>
+    inline void d(fmt::format_string<T...> format, T&&... args) const {
+        log0(LogLevel::debug, format, fmt::make_printf_args(args...));
+    }
+    template <typename... T>
+    inline void d(const InfoHash& f, fmt::format_string<T...> format, T&&... args) const {
+        log1(LogLevel::debug, f, format, fmt::make_printf_args(args...));
+    }
+    template <typename... T>
+    inline void d(const InfoHash& f1, const InfoHash& f2, fmt::format_string<T...> format, T&&... args) const {
+        log2(LogLevel::debug, f1, f2, format, fmt::make_printf_args(args...));
+    }
+    template <typename... T>
+    inline void w(fmt::format_string<T...> format, T&&... args) const {
+        log0(LogLevel::warning, format, fmt::make_printf_args(args...));
+    }
+    template <typename... T>
+    inline void w(const InfoHash& f, fmt::format_string<T...> format, T&&... args) const {
+        log1(LogLevel::warning, f, format, fmt::make_printf_args(args...));
+    }
+    template <typename... T>
+    inline void w(const InfoHash& f1, const InfoHash& f2, fmt::format_string<T...> format, T&&... args) const {
+        log2(LogLevel::warning, f1, f2, format, fmt::make_printf_args(args...));
+    }
+    template <typename... T>
+    inline void e(fmt::format_string<T...> format, T&&... args) const {
+        log0(LogLevel::error, format, fmt::make_printf_args(args...));
+    }
+    template <typename... T>
+    inline void e(const InfoHash& f, fmt::format_string<T...> format, T&&... args) const {
+        log1(LogLevel::error, f, format, fmt::make_printf_args(args...));
+    }
+    template <typename... T>
+    inline void e(const InfoHash& f1, const InfoHash& f2, fmt::format_string<T...> format, T&&... args) const {
+        log2(LogLevel::error, f1, f2, format, fmt::make_printf_args(args...));
+    }
+private:
+    bool filterEnable_ {false};
+    InfoHash filter_ {};
+};
+
+}
+using Logger = log::Logger;
+}
diff --git a/include/opendht/network_engine.h b/include/opendht/network_engine.h
index 4204060..802a8d7 100644
--- a/include/opendht/network_engine.h
+++ b/include/opendht/network_engine.h
@@ -27,7 +27,7 @@
 #include "utils.h"
 #include "rng.h"
 #include "rate_limiter.h"
-#include "log_enable.h"
+#include "logger.h"
 #include "network_utils.h"
 
 #include <vector>
@@ -416,12 +416,19 @@ public:
      *
      * @return the request with information concerning its success.
      */
+    void sendUpdateValues(const Sp<Node>& n,
+                                 const InfoHash& infohash,
+                                 std::vector<Sp<Value>>&& values,
+                                 time_point created,
+                                 const Blob& token,
+                                 size_t sid);
     Sp<Request> sendUpdateValues(const Sp<Node>& n,
                                  const InfoHash& infohash,
-                                 const std::vector<Sp<Value>>& values,
+                                 std::vector<Sp<Value>>::iterator begin,
+                                 std::vector<Sp<Value>>::iterator end,
                                  time_point created,
                                  const Blob& token,
-                                 const size_t& sid);
+                                 size_t sid);
 
     /**
      * Parses a message and calls appropriate callbacks.
@@ -492,6 +499,7 @@ private:
 
     static constexpr size_t MTU {1280};
     static constexpr size_t MAX_PACKET_VALUE_SIZE {600};
+    static constexpr size_t MAX_MESSAGE_VALUE_SIZE {56 * 1024};
 
     static const std::string my_v;
 
@@ -525,7 +533,10 @@ private:
     int send(const SockAddr& addr, const char *buf, size_t len, bool confirmed = false);
 
     void sendValueParts(Tid tid, const std::vector<Blob>& svals, const SockAddr& addr);
-    std::vector<Blob> packValueHeader(msgpack::sbuffer&, const std::vector<Sp<Value>>&);
+    std::vector<Blob> packValueHeader(msgpack::sbuffer&, std::vector<Sp<Value>>::const_iterator, std::vector<Sp<Value>>::const_iterator) const;
+    std::vector<Blob> packValueHeader(msgpack::sbuffer& buf, const std::vector<Sp<Value>>& values) const {
+        return packValueHeader(buf, values.begin(), values.end());
+    }
     void maintainRxBuffer(Tid tid);
 
     /*************
diff --git a/include/opendht/network_utils.h b/include/opendht/network_utils.h
index 238b876..4ffbd06 100644
--- a/include/opendht/network_utils.h
+++ b/include/opendht/network_utils.h
@@ -21,7 +21,7 @@
 
 #include "sockaddr.h"
 #include "utils.h"
-#include "log_enable.h"
+#include "logger.h"
 
 #ifdef _WIN32
 #include <ws2tcpip.h>
diff --git a/include/opendht/node.h b/include/opendht/node.h
index 615089e..907892c 100644
--- a/include/opendht/node.h
+++ b/include/opendht/node.h
@@ -22,6 +22,7 @@
 #include "infohash.h" // includes socket structures
 #include "utils.h"
 #include "sockaddr.h"
+#include "node_export.h"
 
 #include <list>
 #include <map>
@@ -52,7 +53,7 @@ struct Node {
     Node(const InfoHash& id, const sockaddr* sa, socklen_t salen, std::mt19937_64& rd)
         : Node(id, SockAddr(sa, salen), rd) {}
 
-    InfoHash getId() const {
+    const InfoHash& getId() const {
         return id;
     }
     const SockAddr& getAddr() const { return addr; }
@@ -92,8 +93,7 @@ struct Node {
     NodeExport exportNode() const {
         NodeExport ne;
         ne.id = id;
-        ne.sslen = addr.getLength();
-        std::memcpy(&ne.ss, addr.get(), ne.sslen);
+        ne.addr = addr;
         return ne;
     }
     sa_family_t getFamily() const { return addr.getFamily(); }
diff --git a/include/opendht/node_export.h b/include/opendht/node_export.h
new file mode 100644
index 0000000..bb451ac
--- /dev/null
+++ b/include/opendht/node_export.h
@@ -0,0 +1,48 @@
+/*
+ *  Copyright (C) 2014-2023 Savoir-faire Linux Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include "def.h"
+#include "infohash.h"
+#include "sockaddr.h"
+
+#include <string_view>
+
+namespace dht {
+using namespace std::literals;
+
+struct OPENDHT_PUBLIC NodeExport {
+    InfoHash id;
+    SockAddr addr;
+
+    template <typename Packer>
+    void msgpack_pack(Packer& pk) const
+    {
+        pk.pack_map(2);
+        pk.pack("id"sv);
+        pk.pack(id);
+        pk.pack("addr"sv);
+        pk.pack_bin(addr.getLength());
+        pk.pack_bin_body((const char*)addr.get(), (size_t)addr.getLength());
+    }
+
+    void msgpack_unpack(msgpack::object o);
+
+    OPENDHT_PUBLIC friend std::ostream& operator<< (std::ostream& s, const NodeExport& h);
+};
+
+}
diff --git a/include/opendht/peer_discovery.h b/include/opendht/peer_discovery.h
index 610e444..0c63e14 100644
--- a/include/opendht/peer_discovery.h
+++ b/include/opendht/peer_discovery.h
@@ -22,7 +22,7 @@
 #include "def.h"
 #include "sockaddr.h"
 #include "infohash.h"
-#include "log_enable.h"
+#include "logger.h"
 
 #include <thread>
 
diff --git a/include/opendht/securedht.h b/include/opendht/securedht.h
index f73e5a1..19e3d24 100644
--- a/include/opendht/securedht.h
+++ b/include/opendht/securedht.h
@@ -378,7 +378,7 @@ const ValueType CERTIFICATE_TYPE = {
         try {
             crypto::Certificate crt(v->data);
             // TODO check certificate signature
-            return crt.getPublicKey().getId() == id;
+            return crt.getPublicKey().getId() == id || InfoHash::get(crt.getPublicKey().getLongId()) == id;
         } catch (const std::exception& e) {}
         return false;
     },
diff --git a/include/opendht/sockaddr.h b/include/opendht/sockaddr.h
index 717515c..b2e8468 100644
--- a/include/opendht/sockaddr.h
+++ b/include/opendht/sockaddr.h
@@ -20,6 +20,10 @@
 
 #include "def.h"
 
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <fmt/ostream.h>
+
 #ifndef _WIN32
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -48,6 +52,7 @@ typedef uint16_t in_port_t;
 
 namespace dht {
 
+OPENDHT_PUBLIC void print_addr(std::ostream& os, const sockaddr* sa, socklen_t slen);
 OPENDHT_PUBLIC std::string print_addr(const sockaddr* sa, socklen_t slen);
 OPENDHT_PUBLIC std::string print_addr(const sockaddr_storage& ss, socklen_t sslen);
 
@@ -289,6 +294,11 @@ public:
                                (uint8_t*)b.get()+start, len) < 0;
         }
     };
+    OPENDHT_PUBLIC friend std::ostream& operator<< (std::ostream& s, const SockAddr& h) {
+        print_addr(s, h.get(), h.getLength());
+        return s;
+    }
+
 private:
     struct free_delete { void operator()(void* p) { ::free(p); } };
     std::unique_ptr<sockaddr, free_delete> addr {};
@@ -309,3 +319,7 @@ private:
 OPENDHT_PUBLIC bool operator==(const SockAddr& a, const SockAddr& b);
 
 }
+
+#if FMT_VERSION >= 90000
+template <> struct fmt::formatter<dht::SockAddr> : ostream_formatter {};
+#endif
\ No newline at end of file
diff --git a/include/opendht/value.h b/include/opendht/value.h
index 64c5220..ae8776f 100644
--- a/include/opendht/value.h
+++ b/include/opendht/value.h
@@ -475,7 +475,7 @@ struct OPENDHT_PUBLIC Value
     OPENDHT_PUBLIC friend std::ostream& operator<< (std::ostream& s, const Value& v);
 
     inline std::string toString() const {
-        std::stringstream ss;
+        std::ostringstream ss;
         ss << *this;
         return ss.str();
     }
@@ -774,7 +774,7 @@ struct OPENDHT_PUBLIC Select
     }
 
     std::string toString() const {
-        std::stringstream ss;
+        std::ostringstream ss;
         ss << *this;
         return ss.str();
     }
@@ -897,7 +897,7 @@ struct OPENDHT_PUBLIC Where
     }
 
     std::string toString() const {
-        std::stringstream ss;
+        std::ostringstream ss;
         ss << *this;
         return ss.str();
     }
@@ -963,7 +963,7 @@ struct OPENDHT_PUBLIC Query
     void msgpack_unpack(const msgpack::object& o);
 
     std::string toString() const {
-        std::stringstream ss;
+        std::ostringstream ss;
         ss << *this;
         return ss.str();
     }
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..36298bf
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,99 @@
+project('opendht', 'c', 'cpp',
+    version: '2.6.0',
+    default_options: [
+        'cpp_std=c++17',
+        'warning_level=3'
+    ])
+
+gnutls = dependency('gnutls')
+nettle = dependency('nettle')
+msgpack = dependency('msgpack-cxx', required : false)
+argon2 = dependency('libargon2')
+openssl = dependency('openssl', required: get_option('proxy_client'))
+jsoncpp = dependency('jsoncpp', required: get_option('proxy_client'))
+fmt = dependency('fmt')
+
+dirs=[]
+if host_machine.system() == 'freebsd'
+    dirs+='/usr/local/lib'
+elif host_machine.system() == 'darwin'
+    dirs+='/opt/homebrew/lib'
+endif
+http_parser = meson.get_compiler('c').find_library('http_parser', dirs: dirs, required: get_option('proxy_client'))
+deps = [fmt, gnutls, nettle, msgpack, argon2, openssl, jsoncpp, http_parser]
+
+add_project_arguments('-DMSGPACK_NO_BOOST', language : 'cpp')
+add_project_arguments(['-Wno-return-type','-Wno-deprecated','-Wnon-virtual-dtor','-pedantic-errors','-fvisibility=hidden'], language : 'cpp')
+
+opendht_inc = include_directories('include/opendht')
+opendht_interface_inc = include_directories('include', is_system: true)
+opendht_src = [
+    'src/utils.cpp',
+    'src/crypto.cpp',
+    'src/default_types.cpp',
+    'src/node.cpp',
+    'src/value.cpp',
+    'src/dht.cpp',
+    'src/callbacks.cpp',
+    'src/routing_table.cpp',
+    'src/node_cache.cpp',
+    'src/network_engine.cpp',
+    'src/securedht.cpp',
+    'src/dhtrunner.cpp',
+    'src/log.cpp',
+    'src/op_cache.cpp',
+    'src/network_utils.cpp'
+]
+if jsoncpp.found()
+    opendht_src += ['src/base64.cpp']
+    add_project_arguments('-DOPENDHT_JSONCPP', language : 'cpp')
+endif
+if http_parser.found()
+    opendht_src += ['src/http.cpp', 'src/compat/os_cert.cpp']
+    if host_machine.system() == 'darwin'
+        deps+=dependency('appleframeworks', modules : ['CoreFoundation', 'Security'])
+    endif
+endif
+if get_option('proxy_client').enabled()
+    opendht_src += ['src/dht_proxy_client.cpp']
+    add_project_arguments('-DOPENDHT_PROXY_CLIENT', language : 'cpp')
+endif
+if get_option('proxy_server').enabled()
+    opendht_src += 'src/dht_proxy_server.cpp'
+    add_project_arguments('-DOPENDHT_PROXY_SERVER', language : 'cpp')
+endif
+if get_option('peer_discovery').enabled()
+    opendht_src += 'src/peer_discovery.cpp'
+    add_project_arguments('-DOPENDHT_PEER_DISCOVERY', language : 'cpp')
+endif
+opendht = shared_library('opendht',
+    opendht_src,
+    include_directories : opendht_inc,
+    dependencies : deps,
+    cpp_args : ['-DOPENHT_BUILD', '-Dopendht_EXPORTS'],
+    install : true)
+
+readline = meson.get_compiler('c').find_library('readline', required: get_option('tools'))
+if get_option('tools').enabled()
+    dhtnode = executable('dhtnode', 'tools/dhtnode.cpp',
+        include_directories : opendht_interface_inc,
+        link_with : opendht,
+        dependencies : [readline, msgpack],
+        install : true)
+    dhtchat = executable('dhtchat', 'tools/dhtchat.cpp',
+        include_directories : opendht_interface_inc,
+        link_with : opendht,
+        dependencies : [readline, msgpack],
+        install : true)
+    dhtscanner = executable('dhtscanner', 'tools/dhtscanner.cpp',
+        include_directories : opendht_interface_inc,
+        link_with : opendht,
+        dependencies : [readline, msgpack],
+        install : true)
+    if http_parser.found()
+        durl = executable('durl', 'tools/durl.cpp',
+        include_directories : opendht_interface_inc,
+        link_with : opendht,
+        dependencies : [msgpack, openssl])
+    endif
+endif
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 0000000..2901795
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,4 @@
+option('proxy_client', type : 'feature', value : 'disabled')
+option('proxy_server', type : 'feature', value : 'disabled')
+option('peer_discovery', type : 'feature', value : 'enabled')
+option('tools', type : 'feature', value : 'enabled')
diff --git a/opendht.pc.in b/opendht.pc.in
index 30dd639..b9c835c 100644
--- a/opendht.pc.in
+++ b/opendht.pc.in
@@ -7,5 +7,6 @@ Description: C++17 Distributed Hash Table library
 Version: @VERSION@
 Libs: -L${libdir} -lopendht
 Libs.private: @http_parser_lib@ -pthread
-Requires.private: gnutls >= 3.3, nettle >= 2.4@argon2_lib@@jsoncpp_lib@@openssl_lib@
+Requires: gnutls >= 3.3@jsoncpp_lib@@openssl_lib@
+Requires.private: nettle >= 2.4@argon2_lib@
 Cflags: -I${includedir}
diff --git a/python/tools/http_server.py b/python/tools/http_server.py
index db9d357..64974f0 100755
--- a/python/tools/http_server.py
+++ b/python/tools/http_server.py
@@ -72,7 +72,7 @@ if __name__ == '__main__':
     parser = argparse.ArgumentParser(description='Launch an OpenDHT node with an HTTP control interface')
     parser.add_argument('-p', '--port', help='OpenDHT port to bind', type=int, default=4222)
     parser.add_argument('-hp', '--http-port', help='HTTP port to bind', type=int, default=8080)
-    parser.add_argument('-b', '--bootstrap', help='bootstrap address', default="bootstrap.ring.cx:4222")
+    parser.add_argument('-b', '--bootstrap', help='bootstrap address', default="bootstrap.jami.net:4222")
     args = parser.parse_args()
     endpoints.serverFromString(reactor, "tcp:"+str(args.http_port)).listen(server.Site(DhtServer(args.port, args.bootstrap)))
     reactor.run()
diff --git a/python/tools/network_monitor.py b/python/tools/network_monitor.py
index 185415d..87223ca 100755
--- a/python/tools/network_monitor.py
+++ b/python/tools/network_monitor.py
@@ -24,7 +24,7 @@ from datetime import datetime
 import opendht as dht
 
 parser = argparse.ArgumentParser(description='Create a dht network of -n nodes')
-parser.add_argument('-b', '--bootstrap', help='bootstrap address', default='bootstrap.ring.cx')
+parser.add_argument('-b', '--bootstrap', help='bootstrap address', default='bootstrap.jami.net')
 parser.add_argument('-n', '--num-ops', help='number of concurrent operations on the DHT', type=int, default=8)
 parser.add_argument('-p', '--period', help='duration between each test (seconds)', type=int, default=60)
 parser.add_argument('-t', '--timeout', help='timeout for a test to complete (seconds)', type=float, default=15)
diff --git a/python/tools/pingpong.py b/python/tools/pingpong.py
index ef0d54a..02545ba 100755
--- a/python/tools/pingpong.py
+++ b/python/tools/pingpong.py
@@ -25,8 +25,7 @@ config.setRateLimit(-1, -1)
 ping_node = dht.DhtRunner()
 ping_node.run(config=config)
 #ping_node.enableLogging()
-#ping_node.bootstrap("bootstrap.ring.cx", "4222")
-
+#ping_node.bootstrap("bootstrap.jami.net", "4222")
 pong_node = dht.DhtRunner()
 pong_node.run(config=config)
 #pong_node.enableLogging()
diff --git a/src/Makefile.am b/src/Makefile.am
index acaccf7..43afdde 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -19,7 +19,6 @@ libopendht_la_SOURCES  = \
         routing_table.cpp \
         network_engine.cpp \
         utils.cpp \
-        infohash.cpp \
         node.cpp \
         value.cpp \
         crypto.cpp \
@@ -40,6 +39,7 @@ nobase_include_HEADERS = \
         ../include/opendht/dht.h \
         ../include/opendht/callbacks.h \
         ../include/opendht/node_cache.h \
+        ../include/opendht/node_export.h \
         ../include/opendht/routing_table.h \
         ../include/opendht/network_engine.h \
         ../include/opendht/scheduler.h \
@@ -54,7 +54,7 @@ nobase_include_HEADERS = \
         ../include/opendht/dhtrunner.h \
         ../include/opendht/default_types.h \
         ../include/opendht/log.h \
-        ../include/opendht/log_enable.h \
+        ../include/opendht/logger.h \
         ../include/opendht/network_utils.h \
         ../include/opendht/rng.h \
         ../include/opendht/thread_pool.h
diff --git a/src/callbacks.cpp b/src/callbacks.cpp
index ecda405..b98ab5f 100644
--- a/src/callbacks.cpp
+++ b/src/callbacks.cpp
@@ -92,7 +92,7 @@ bindDoneCbSimple(DoneCallbackSimpleRaw raw_cb, void* user_data) {
 std::string
 NodeStats::toString() const
 {
-    std::stringstream ss;
+    std::ostringstream ss;
     ss << "Known nodes: " << good_nodes << " good, " << dubious_nodes << " dubious, " << incoming_nodes << " incoming." << std::endl;
     ss << searches << " searches, " << node_cache_size << " total cached nodes" << std::endl;
     if (table_depth > 1) {
diff --git a/src/compat/os_cert.cpp b/src/compat/os_cert.cpp
index ea89f12..11163a2 100644
--- a/src/compat/os_cert.cpp
+++ b/src/compat/os_cert.cpp
@@ -18,7 +18,7 @@
 
 #include "os_cert.h"
 
-#include "log_enable.h"
+#include "logger.h"
 
 #ifdef _WIN32
 #include <windows.h>
diff --git a/src/compat/os_cert.h b/src/compat/os_cert.h
index f20bd72..43d9d75 100644
--- a/src/compat/os_cert.h
+++ b/src/compat/os_cert.h
@@ -18,7 +18,7 @@
 
 #pragma once
 
-#include "log_enable.h"
+#include "logger.h"
 
 #include <memory>
 
diff --git a/src/crypto.cpp b/src/crypto.cpp
index dba3473..ff997d6 100644
--- a/src/crypto.cpp
+++ b/src/crypto.cpp
@@ -366,7 +366,7 @@ PrivateKey::getSharedPublicKey() const
         auto pk = std::make_shared<PublicKey>();
         if (auto err = gnutls_pubkey_import_privkey(pk->pk, key, GNUTLS_KEY_KEY_CERT_SIGN | GNUTLS_KEY_CRL_SIGN, 0))
             throw CryptoException(std::string("Can't retreive public key: ") + gnutls_strerror(err));
-        publicKey_ = pk;
+        publicKey_ = std::move(pk);
     }
     return publicKey_;
 }
@@ -824,13 +824,23 @@ Certificate::~Certificate()
     }
 }
 
-PublicKey
+const PublicKey&
 Certificate::getPublicKey() const
 {
-    PublicKey pk_ret;
-    if (auto err = gnutls_pubkey_import_x509(pk_ret.pk, cert, 0))
-        throw CryptoException(std::string("Can't get certificate public key: ") + gnutls_strerror(err));
-    return pk_ret;
+    return *getSharedPublicKey();
+}
+
+const std::shared_ptr<PublicKey>&
+Certificate::getSharedPublicKey() const
+{
+    std::lock_guard<std::mutex> lock(publicKeyMutex_);
+    if (not publicKey_) {
+        auto pk = std::make_shared<PublicKey>();
+        if (auto err = gnutls_pubkey_import_x509(pk->pk, cert, 0))
+            throw CryptoException(std::string("Can't get certificate public key: ") + gnutls_strerror(err));
+        publicKey_ = std::move(pk);
+    }
+    return publicKey_;
 }
 
 const InfoHash&
diff --git a/src/dht.cpp b/src/dht.cpp
index 8449526..6c6c0c7 100644
--- a/src/dht.cpp
+++ b/src/dht.cpp
@@ -870,7 +870,7 @@ Dht::listenTo(const InfoHash& id, sa_family_t af, ValueCallback cb, Value::Filte
         throw DhtException("Can't create search");
     if (logger_)
         logger_->w(id, "[search %s IPv%c] listen", id.to_c_str(), (af == AF_INET) ? '4' : '6');
-    return sr->listen(cb, f, q, scheduler);
+    return sr->listen(cb, std::move(f), q, scheduler);
 }
 
 size_t
@@ -889,7 +889,7 @@ Dht::listen(const InfoHash& id, ValueCallback cb, Value::Filter f, Where where)
     });
 
     auto query = std::make_shared<Query>(Select{}, std::move(where));
-    auto filter = f.chain(query->where.getFilter());
+    auto filter = Value::Filter::chain(std::move(f), query->where.getFilter());
     auto st = store.find(id);
     if (st == store.end() && store.size() < max_store_keys)
         st = store.emplace(id, scheduler.time() + MAX_STORAGE_MAINTENANCE_EXPIRE_TIME).first;
@@ -1642,7 +1642,7 @@ Dht::dumpSearch(const Search& sr, std::ostream& out) const
 void
 Dht::dumpTables() const
 {
-    std::stringstream out;
+    std::ostringstream out;
     out << "My id " << myid << std::endl;
 
     out << "Buckets IPv4 :" << std::endl;
@@ -1694,7 +1694,7 @@ Dht::getStorageLog(const InfoHash& h) const
 {
     auto s = store.find(h);
     if (s == store.end()) {
-        std::stringstream out;
+        std::ostringstream out;
         out << "Storage " << h << " empty" << std::endl;
         return out.str();
     }
@@ -1704,7 +1704,7 @@ Dht::getStorageLog(const InfoHash& h) const
 std::string
 Dht::printStorageLog(const decltype(store)::value_type& s) const
 {
-    std::stringstream out;
+    std::ostringstream out;
     using namespace std::chrono;
     const auto& st = s.second;
     out << "Storage " << s.first << " "
@@ -1723,7 +1723,7 @@ Dht::printStorageLog(const decltype(store)::value_type& s) const
 std::string
 Dht::getRoutingTablesLog(sa_family_t af) const
 {
-    std::stringstream out;
+    std::ostringstream out;
     for (const auto& b : buckets(af))
         dumpBucket(b, out);
     return out.str();
@@ -1732,7 +1732,7 @@ Dht::getRoutingTablesLog(sa_family_t af) const
 std::string
 Dht::getSearchesLog(sa_family_t af) const
 {
-    std::stringstream out;
+    std::ostringstream out;
     auto num_searches = dht4.searches.size() + dht6.searches.size();
     if (num_searches > 8) {
         if (not af or af == AF_INET)
@@ -1757,7 +1757,7 @@ Dht::getSearchesLog(sa_family_t af) const
 std::string
 Dht::getSearchLog(const InfoHash& id, sa_family_t af) const
 {
-    std::stringstream out;
+    std::ostringstream out;
     if (af == AF_UNSPEC) {
         out << getSearchLog(id, AF_INET) << getSearchLog(id, AF_INET6);
     } else {
@@ -1840,7 +1840,7 @@ Dht::Dht(std::unique_ptr<net::DatagramSocket>&& sock, const Config& config, cons
     expire();
 
     if (logger_)
-        logger_->d("DHT node initialised with ID %s", myid.toString().c_str());
+        logger_->debug("DHT node initialised with ID {:s}", myid);
 }
 
 bool
@@ -2232,7 +2232,7 @@ Dht::pingNode(SockAddr sa, DoneCallbackSimple&& cb)
 {
     scheduler.syncTime();
     if (logger_)
-        logger_->d("Sending ping to %s", sa.toString().c_str());
+        logger_->debug("Sending ping to {}", sa);
     auto& count = dht(sa.getFamily()).pending_pings;
     count++;
     network_engine.sendPing(std::move(sa), [&count,cb](const net::Request&, net::RequestAnswer&&) {
@@ -2638,7 +2638,7 @@ Dht::loadState(const std::string& path)
             std::vector<Sp<Node>> tmpNodes;
             tmpNodes.reserve(state.nodes.size());
             for (const auto& node : state.nodes)
-                tmpNodes.emplace_back(network_engine.insertNode(node.id, SockAddr(node.ss, node.sslen)));
+                tmpNodes.emplace_back(network_engine.insertNode(node.id, node.addr));
             importValues(state.values);
         }
     } catch (const std::exception& e) {
diff --git a/src/dht_proxy_server.cpp b/src/dht_proxy_server.cpp
index 1b4b177..7ac9a48 100644
--- a/src/dht_proxy_server.cpp
+++ b/src/dht_proxy_server.cpp
@@ -631,6 +631,8 @@ DhtProxyServer::createRestRouter()
     // key.listen
     router->http_get("/key/:hash/listen", std::bind(&DhtProxyServer::listen, this, _1, _2));
 #ifdef OPENDHT_PUSH_NOTIFICATIONS
+    // node.pingPush
+    router->http_post("/node/pingPush", std::bind(&DhtProxyServer::pingPush, this, _1, _2));
     // key.subscribe
     router->add_handler(restinio::http_method_subscribe(),
                         "/key/:hash", std::bind(&DhtProxyServer::subscribe, this, _1, _2));
@@ -702,7 +704,7 @@ DhtProxyServer::get(restinio::request_handle_t request,
             initHttpResponse(request->create_response<ResponseByParts>()));
         response->flush();
         dht_->get(infoHash, [this, response](const std::vector<Sp<Value>>& values) {
-            std::stringstream output;
+            std::ostringstream output;
             for (const auto& value : values) {
                 output << Json::writeString(jsonBuilder_, value->toJson()) << "\n";
             }
@@ -772,6 +774,43 @@ DhtProxyServer::getDefaultTopic(PushType) {
     return bundleId_;
 }
 
+RequestStatus
+DhtProxyServer::pingPush(restinio::request_handle_t request,
+                         restinio::router::route_params_t /*params*/)
+{
+    requestNum_++;
+    try {
+        std::string err;
+        Json::Value r;
+        auto* char_data = reinterpret_cast<const char*>(request->body().data());
+        auto reader = std::unique_ptr<Json::CharReader>(jsonReaderBuilder_.newCharReader());
+        if (!reader->parse(char_data, char_data + request->body().size(), &r, &err)){
+            auto response = initHttpResponse(request->create_response(restinio::status_bad_request()));
+            response.set_body(RESP_MSG_JSON_INCORRECT);
+            return response.done();
+        }
+        const Json::Value& root(r); // parse using const Json so [] never creates element
+        auto pushToken = root["key"].asString();
+        if (pushToken.empty()){
+            auto response = initHttpResponse(request->create_response(restinio::status_bad_request()));
+            response.set_body(RESP_MSG_NO_TOKEN);
+            return response.done();
+        }
+        auto type = getTypeFromString(root["platform"].asString());
+        auto topic = root["topic"].asString();
+        if (topic.empty()) {
+            topic = getDefaultTopic(type);
+        }
+        Json::Value json;
+        json["to"] = root["client_id"];
+        json["pong"] = true;
+        sendPushNotification(pushToken, std::move(json), type, true, topic);
+    } catch (...) {
+        return serverError(*request);
+    }
+    return restinio::request_handling_status_t::accepted;
+}
+
 RequestStatus
 DhtProxyServer::subscribe(restinio::request_handle_t request,
                           restinio::router::route_params_t params)
diff --git a/src/dhtrunner.cpp b/src/dhtrunner.cpp
index f687fdd..7cbc910 100644
--- a/src/dhtrunner.cpp
+++ b/src/dhtrunner.cpp
@@ -93,7 +93,7 @@ DhtRunner::run(const Config& config, Context&& context)
     auto expected = State::Idle;
     if (not running.compare_exchange_strong(expected, State::Running)) {
         if (context.logger)
-            context.logger->w("[runner %p] Node is already running. Call join() first before calling run() again.", this);
+            context.logger->w("[runner %p] Node is already running. Call join() first before calling run() again.", fmt::ptr(this));
         return;
     }
 
@@ -102,7 +102,7 @@ DhtRunner::run(const Config& config, Context&& context)
         auto local6 = config.bind6;
         if (not local4 and not local6) {
             if (context.logger)
-                context.logger->w("[runner %p] No address to bind specified in the configuration, using default addresses", this);
+                context.logger->w("[runner %p] No address to bind specified in the configuration, using default addresses", fmt::ptr(this));
             local4.setFamily(AF_INET);
             local6.setFamily(AF_INET6);
         }
@@ -116,14 +116,14 @@ DhtRunner::run(const Config& config, Context&& context)
                 if (inConfig >> port) {
                     if (local4.getPort() == 0) {
                         if (context.logger)
-                            context.logger->d("[runner %p] Using IPv4 port %hu from saved configuration", this, port);
+                            context.logger->d("[runner %p] Using IPv4 port %hu from saved configuration", fmt::ptr(this), port);
                         local4.setPort(port);
                     }
                 }
                 if (inConfig >> port) {
                     if (local6.getPort() == 0) {
                         if (context.logger)
-                            context.logger->d("[runner %p] Using IPv6 port %hu from saved configuration", this, port);
+                            context.logger->d("[runner %p] Using IPv6 port %hu from saved configuration", fmt::ptr(this), port);
                         local6.setPort(port);
                     }
                 }
@@ -132,7 +132,7 @@ DhtRunner::run(const Config& config, Context&& context)
 
         if (context.logger) {
             logger_ = context.logger;
-            logger_->d("[runner %p] state changed to Running", this);
+            logger_->d("[runner %p] state changed to Running", fmt::ptr(this));
         }
 
 #ifdef OPENDHT_PROXY_CLIENT
@@ -155,7 +155,7 @@ DhtRunner::run(const Config& config, Context&& context)
                         dropped++;
                     }
                     if (dropped and logger_) {
-                        logger_->w("[runner %p] dropped %zu packets: queue is full!", this, dropped);
+                        logger_->w("[runner %p] dropped %zu packets: queue is full!", fmt::ptr(this), dropped);
                     }
                     ret = std::move(rcv_free);
                 }
@@ -281,7 +281,7 @@ DhtRunner::shutdown(ShutdownCallback cb, bool stop) {
         return;
     }
     if (logger_)
-        logger_->d("[runner %p] state changed to Stopping, %zu ongoing ops", this, ongoing_ops.load());
+        logger_->d("[runner %p] state changed to Stopping, %zu ongoing ops", fmt::ptr(this), ongoing_ops.load());
     ongoing_ops++;
     shutdownCallbacks_.emplace_back(std::move(cb));
     pending_ops.emplace([=](SecureDht&) mutable {
@@ -346,7 +346,7 @@ DhtRunner::join()
             if (auto sock = dht_->getSocket())
                 sock->stop();
         if (logger_)
-            logger_->d("[runner %p] state changed to Idle", this);
+            logger_->d("[runner %p] state changed to Idle", fmt::ptr(this));
     }
 
     if (dht_thread.joinable())
@@ -355,7 +355,7 @@ DhtRunner::join()
     {
         std::lock_guard<std::mutex> lck(storage_mtx);
         if (ongoing_ops and logger_) {
-            logger_->w("[runner %p] stopping with %zu remaining ops", this, ongoing_ops.load());
+            logger_->w("[runner %p] stopping with %zu remaining ops", fmt::ptr(this), ongoing_ops.load());
         }
         pending_ops = decltype(pending_ops)();
         pending_ops_prio = decltype(pending_ops_prio)();
@@ -454,12 +454,6 @@ DhtRunner::setLogger(const Sp<Logger>& logger) {
         dht_->setLogger(logger);
 }
 
-void
-DhtRunner::setLoggers(LogMethod error, LogMethod warn, LogMethod debug) {
-    Logger logger {std::move(error), std::move(warn), std::move(debug)};
-    setLogger(logger);
-}
-
 void
 DhtRunner::setLogFilter(const InfoHash& f) {
     std::lock_guard<std::mutex> lck(dht_mtx);
@@ -686,7 +680,7 @@ DhtRunner::loop_()
     }
 
     if (dropped && logger_)
-        logger_->e("[runner %p] Dropped %zu packets with high delay.", this, dropped);
+        logger_->e("[runner %p] Dropped %zu packets with high delay.", fmt::ptr(this), dropped);
 
     NodeStatus nstatus4 = dht_->updateStatus(AF_INET);
     NodeStatus nstatus6 = dht_->updateStatus(AF_INET6);
@@ -1088,6 +1082,7 @@ DhtRunner::setProxyServer(const std::string& proxy, const std::string& pushNodeI
 #else
     if (not proxy.empty())
         throw std::runtime_error("DHT proxy requested but OpenDHT built without proxy support.");
+    (void) pushNodeId;
 #endif
 }
 
diff --git a/src/http.cpp b/src/http.cpp
index 40aca3c..a34005d 100644
--- a/src/http.cpp
+++ b/src/http.cpp
@@ -17,7 +17,7 @@
  */
 
 #include "http.h"
-#include "log_enable.h"
+#include "logger.h"
 #include "crypto.h"
 #include "base64.h"
 #include "compat/os_cert.h"
@@ -87,7 +87,7 @@ Url::Url(const std::string& url): url(url)
 std::string
 Url::toString() const
 {
-    std::stringstream ss;
+    std::ostringstream ss;
     if (not protocol.empty()) {
         ss << protocol << "://";
     }
@@ -111,11 +111,11 @@ newTlsClientContext(const std::shared_ptr<dht::Logger>& logger)
 
     if (char* path = getenv("CA_ROOT_FILE")) {
         if (logger)
-            logger->d("Using CA file: %s", path);
+            logger->debug("Using CA file: {}", path);
         ctx->load_verify_file(path);
     } else if (char* path = getenv("CA_ROOT_PATH")) {
         if (logger)
-            logger->d("Using CA path: %s", path);
+            logger->debug("Using CA path: {}", path);
         ctx->add_verify_path(path);
     } else {
 #ifdef __ANDROID__
@@ -1044,7 +1044,7 @@ Request::set_auth(const std::string& username, const std::string& password)
 void
 Request::build()
 {
-    std::stringstream request;
+    std::ostringstream request;
     bool append_body = !body_.empty();
 
     // first header
diff --git a/src/indexation/pht.cpp b/src/indexation/pht.cpp
index 0840c0e..3aba81d 100644
--- a/src/indexation/pht.cpp
+++ b/src/indexation/pht.cpp
@@ -32,7 +32,7 @@ namespace indexation {
  * @return string that represent the blob into a readable way
  */
 static std::string blobToString(const Blob &bl) {
-    std::stringstream ss;
+    std::ostringstream ss;
     auto bn = bl.size() % 8;
     auto n = bl.size() / 8;
 
@@ -46,7 +46,7 @@ static std::string blobToString(const Blob &bl) {
 }
 
 std::string Prefix::toString() const {
-    std::stringstream ss;
+    std::ostringstream ss;
 
     ss << "Prefix : " << std::endl << "\tContent_ : \"";
     ss << blobToString(content_);
diff --git a/src/infohash.cpp b/src/infohash.cpp
deleted file mode 100644
index 84d04ae..0000000
--- a/src/infohash.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- *  Copyright (C) 2014-2022 Savoir-faire Linux Inc.
- *  Author : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#include "infohash.h"
-
-#include <functional>
-#include <sstream>
-#include <cstdio>
-
-using namespace std::literals;
-
-namespace dht {
-
-const HexMap hex_map = {};
-
-void
-NodeExport::msgpack_unpack(msgpack::object o)
-{
-    if (o.type != msgpack::type::MAP)
-        throw msgpack::type_error();
-    if (o.via.map.size < 2)
-        throw msgpack::type_error();
-    if (o.via.map.ptr[0].key.as<std::string_view>() != "id"sv)
-        throw msgpack::type_error();
-    if (o.via.map.ptr[1].key.as<std::string_view>() != "addr"sv)
-        throw msgpack::type_error();
-    const auto& addr = o.via.map.ptr[1].val;
-    if (addr.type != msgpack::type::BIN)
-        throw msgpack::type_error();
-    if (addr.via.bin.size > sizeof(sockaddr_storage))
-        throw msgpack::type_error();
-    id.msgpack_unpack(o.via.map.ptr[0].val);
-    sslen = addr.via.bin.size;
-    std::copy_n(addr.via.bin.ptr, addr.via.bin.size, (char*)&ss);
-}
-
-std::ostream& operator<< (std::ostream& s, const NodeExport& h)
-{
-    msgpack::pack(s, h);
-    return s;
-}
-
-}
diff --git a/src/log.cpp b/src/log.cpp
index 100f0f7..7d59bab 100644
--- a/src/log.cpp
+++ b/src/log.cpp
@@ -20,6 +20,10 @@
 #include "log.h"
 #include "dhtrunner.h"
 
+#include <fmt/format.h>
+#include <fmt/ostream.h>
+#include <fmt/printf.h>
+
 #ifndef _WIN32
 #include <syslog.h>
 #endif
@@ -30,46 +34,79 @@
 namespace dht {
 namespace log {
 
+/**
+ * Terminal colors for logging
+ */
+namespace Color {
+    enum Code {
+        FG_RED      = 31,
+        FG_GREEN    = 32,
+        FG_YELLOW   = 33,
+        FG_BLUE     = 34,
+        FG_DEFAULT  = 39,
+        BG_RED      = 41,
+        BG_GREEN    = 42,
+        BG_BLUE     = 44,
+        BG_DEFAULT  = 49
+    };
+    class Modifier {
+        const Code code;
+    public:
+        constexpr Modifier(Code pCode) : code(pCode) {}
+        friend std::ostream&
+        operator<<(std::ostream& os, const Modifier& mod) {
+            return os << "\033[" << mod.code << 'm';
+        }
+    };
+}
+
+constexpr const Color::Modifier def(Color::FG_DEFAULT);
+constexpr const Color::Modifier red(Color::FG_RED);
+constexpr const Color::Modifier yellow(Color::FG_YELLOW);
+
 /**
  * Print va_list to std::ostream (used for logging).
  */
 void
-printLog(std::ostream& s, char const *m, va_list args) {
-    // print log to buffer
-    std::array<char, 8192> buffer;
-    int ret = vsnprintf(buffer.data(), buffer.size(), m, args);
-    if (ret <= 0)
-        return;
-
-    // write timestamp
+printfLog(std::ostream& s, fmt::string_view format, fmt::printf_args args) {
     using namespace std::chrono;
     using log_precision = microseconds;
+    auto num = duration_cast<log_precision>(steady_clock::now().time_since_epoch()).count();
     constexpr auto den = log_precision::period::den;
+    fmt::print(s, "[{:06d}.{:06d}] ", num / den, num % den);
+    s << fmt::vsprintf(format, args);
+    s << std::endl;
+}
+void
+printLog(std::ostream& s, fmt::string_view format, fmt::format_args args) {
+    using namespace std::chrono;
+    using log_precision = microseconds;
     auto num = duration_cast<log_precision>(steady_clock::now().time_since_epoch()).count();
-    s << "[" << std::setfill('0') << std::setw(6) << num / den << "."
-             << std::setfill('0') << std::setw(6) << num % den << "]" << " ";
-
-    // write log
-    s.write(buffer.data(), std::min((size_t) ret, buffer.size()));
-    if ((size_t) ret >= buffer.size())
-        s << "[[TRUNCATED]]";
+    constexpr auto den = log_precision::period::den;
+    fmt::print(s, "[{:06d}.{:06d}] ", num / den, num % den);
+    fmt::vprint(s, format, args);
     s << std::endl;
 }
 
 std::shared_ptr<Logger>
 getStdLogger() {
     return std::make_shared<Logger>(
-        [](char const *m, va_list args) {
-            std::cerr << red;
-            printLog(std::cerr, m, args);
+        [](LogLevel level, fmt::string_view format, fmt::format_args args) {
+            if (level == LogLevel::error)
+                std::cerr << red;
+            else if (level == LogLevel::warning)
+                std::cerr << yellow;
+            printLog(std::cerr, format, args);
             std::cerr << def;
         },
-        [](char const *m, va_list args) {
-            std::cout << yellow;
-            printLog(std::cout, m, args);
-            std::cout << def;
-        },
-        [](char const *m, va_list args) { printLog(std::cout, m, args); }
+        [](LogLevel level, fmt::string_view format, fmt::printf_args args) {
+            if (level == LogLevel::error)
+                std::cerr << red;
+            else if (level == LogLevel::warning)
+                std::cerr << yellow;
+            printfLog(std::cerr, format, args);
+            std::cerr << def;
+        }
     );
 }
 
@@ -77,14 +114,29 @@ std::shared_ptr<Logger>
 getFileLogger(const std::string &path) {
     auto logfile = std::make_shared<std::ofstream>();
     logfile->open(path, std::ios::out);
-
     return std::make_shared<Logger>(
-        [=](char const *m, va_list args) { printLog(*logfile, m, args); },
-        [=](char const *m, va_list args) { printLog(*logfile, m, args); },
-        [=](char const *m, va_list args) { printLog(*logfile, m, args); }
+        [logfile](LogLevel level, fmt::string_view format, fmt::format_args args) {
+            printLog(*logfile, format, args);
+        },
+        [logfile](LogLevel level, fmt::string_view format, fmt::printf_args args) {
+            printfLog(*logfile, format, args);
+        }
     );
 }
 
+constexpr
+int syslogLevel(LogLevel level) {
+    switch (level) {
+    case LogLevel::error:
+        return LOG_ERR;
+    case LogLevel::warning:
+        return LOG_WARNING;
+    case LogLevel::debug:
+        return LOG_INFO;
+    }
+    return LOG_ERR;
+}
+
 std::shared_ptr<Logger>
 getSyslogLogger(const char* name) {
 #ifndef _WIN32
@@ -104,10 +156,13 @@ getSyslogLogger(const char* name) {
         opened_logfile = logfile;
     }
     return std::make_shared<Logger>(
-        [logfile](char const *m, va_list args) { vsyslog(LOG_ERR, m, args); },
-        [logfile](char const *m, va_list args) { vsyslog(LOG_WARNING, m, args); },
-        [logfile](char const *m, va_list args) { vsyslog(LOG_INFO, m, args); }
-    );
+        [logfile](LogLevel level, fmt::string_view format, fmt::format_args args) {
+            syslog(syslogLevel(level), "%s", fmt::vformat(format, args).c_str());
+        },
+        [logfile](LogLevel level, fmt::string_view format, fmt::printf_args args) {
+            auto fmt = fmt::vsprintf(format, args);
+            syslog(syslogLevel(level), "%s", fmt.data());
+        });
 #else
     return std::make_shared<Logger>();
 #endif
diff --git a/src/network_engine.cpp b/src/network_engine.cpp
index b25bd9c..c641b5d 100644
--- a/src/network_engine.cpp
+++ b/src/network_engine.cpp
@@ -20,7 +20,7 @@
 #include "network_engine.h"
 #include "request.h"
 #include "default_types.h"
-#include "log_enable.h"
+#include "logger.h"
 #include "parsed_message.h"
 
 #include <msgpack.hpp>
@@ -124,9 +124,9 @@ NetworkEngine::tellListener(const Sp<Node>& node, Tid socket_id, const InfoHash&
     auto nnodes = bufferNodes(node->getFamily(), hash, want, nodes, nodes6);
     try {
         if (version >= 1) {
-            sendUpdateValues(node, hash, values, scheduler.time(), ntoken, socket_id);
+            sendUpdateValues(node, hash, std::move(values), scheduler.time(), ntoken, socket_id);
         } else {
-            sendNodesValues(node->getAddr(), socket_id, nnodes.first, nnodes.second, values, query, ntoken);
+            sendNodesValues(node->getAddr(), socket_id, nnodes.first, nnodes.second, std::move(values), query, ntoken);
         }
     } catch (const std::overflow_error& e) {
         if (logger_)
@@ -501,9 +501,8 @@ NetworkEngine::processMessage(const uint8_t *buf, size_t buflen, SockAddr f)
             pmsg.last_part = now;
             scheduler.add(now + RX_MAX_PACKET_TIME, std::bind(&NetworkEngine::maintainRxBuffer, this, k));
             scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, k));
-        } else
-            if (logger_)
-                logger_->e("Partial message with given TID already exists");
+        } else if (logger_)
+            logger_->e("Partial message with given TID %u already exists", k);
     }
 }
 
@@ -912,12 +911,16 @@ NetworkEngine::deserializeNodes(ParsedMessage& msg, const SockAddr& from) {
 }
 
 std::vector<Blob>
-NetworkEngine::packValueHeader(msgpack::sbuffer& buffer, const std::vector<Sp<Value>>& st)
+NetworkEngine::packValueHeader(msgpack::sbuffer& buffer, std::vector<Sp<Value>>::const_iterator b, std::vector<Sp<Value>>::const_iterator e) const
 {
-    auto svals = serializeValues(st);
+    std::vector<Blob> svals;
     size_t total_size = 0;
-    for (const auto& v : svals)
-        total_size += v.size();
+
+    svals.reserve(std::distance(b, e));
+    for (; b != e; ++b) {
+        svals.emplace_back(packMsg(*b));
+        total_size += svals.back().size();
+    }
 
     msgpack::packer<msgpack::sbuffer> pk(&buffer);
     pk.pack(KEY_REQ_VALUES);
@@ -1200,13 +1203,39 @@ NetworkEngine::sendAnnounceValue(const Sp<Node>& n,
     return req;
 }
 
+void
+NetworkEngine::sendUpdateValues(const Sp<Node>& n,
+                                const InfoHash& infohash,
+                                std::vector<Sp<Value>>&& values,
+                                time_point created,
+                                const Blob& token,
+                                size_t socket_id)
+{
+    size_t total_size = 0;
+
+    auto b = values.begin(), e = values.begin();
+    while (e != values.end()) {
+        if (total_size >= MAX_MESSAGE_VALUE_SIZE) {
+            sendUpdateValues(n, infohash, b, e, created, token, socket_id);
+            b = e;
+            total_size = 0;
+        }
+        total_size += (*e)->size();
+        ++e;
+    }
+    if (b != e) {
+        sendUpdateValues(n, infohash, b, e, created, token, socket_id);
+    }
+}
+
 Sp<Request>
 NetworkEngine::sendUpdateValues(const Sp<Node>& n,
                                 const InfoHash& infohash,
-                                const std::vector<Sp<Value>>& values,
+                                std::vector<Sp<Value>>::iterator begin,
+                                std::vector<Sp<Value>>::iterator end,
                                 time_point created,
                                 const Blob& token,
-                                const size_t& socket_id)
+                                size_t socket_id)
 {
     Tid tid (n->getNewTid());
     Tid sid (socket_id);
@@ -1220,7 +1249,7 @@ NetworkEngine::sendUpdateValues(const Sp<Node>& n,
       pk.pack(KEY_VERSION);    pk.pack(1);
       pk.pack(KEY_REQ_H);      pk.pack(infohash);
       pk.pack(KEY_REQ_SID);   pk.pack(sid);
-      auto v = packValueHeader(buffer, values);
+      auto v = packValueHeader(buffer, begin, end);
       if (created < scheduler.time()) {
           pk.pack(KEY_REQ_CREATION);
           pk.pack(to_time_t(created));
diff --git a/src/node.cpp b/src/node.cpp
index c444bbe..1e7df28 100644
--- a/src/node.cpp
+++ b/src/node.cpp
@@ -160,7 +160,7 @@ Node::closeSocket(Tid id)
 std::string
 Node::toString() const
 {
-    std::stringstream ss;
+    std::ostringstream ss;
     ss << (*this);
     return ss.str();
 }
@@ -171,4 +171,30 @@ std::ostream& operator<< (std::ostream& s, const Node& h)
     return s;
 }
 
+void
+NodeExport::msgpack_unpack(msgpack::object o)
+{
+    if (o.type != msgpack::type::MAP)
+        throw msgpack::type_error();
+    if (o.via.map.size < 2)
+        throw msgpack::type_error();
+    if (o.via.map.ptr[0].key.as<std::string_view>() != "id"sv)
+        throw msgpack::type_error();
+    if (o.via.map.ptr[1].key.as<std::string_view>() != "addr"sv)
+        throw msgpack::type_error();
+    const auto& maddr = o.via.map.ptr[1].val;
+    if (maddr.type != msgpack::type::BIN)
+        throw msgpack::type_error();
+    if (maddr.via.bin.size > sizeof(sockaddr_storage))
+        throw msgpack::type_error();
+    id.msgpack_unpack(o.via.map.ptr[0].val);
+    addr = {(const sockaddr*)maddr.via.bin.ptr, (socklen_t)maddr.via.bin.size};
+}
+
+std::ostream& operator<< (std::ostream& s, const NodeExport& h)
+{
+    msgpack::pack(s, h);
+    return s;
+}
+
 }
diff --git a/src/op_cache.cpp b/src/op_cache.cpp
index fa66583..a918e5c 100644
--- a/src/op_cache.cpp
+++ b/src/op_cache.cpp
@@ -175,7 +175,7 @@ SearchCache::getOp(const Sp<Query>& q) const
 }
 
 size_t
-SearchCache::listen(const ValueCallback& get_cb, const Sp<Query>& q, const Value::Filter& filter, const OnListen& onListen)
+SearchCache::listen(const ValueCallback& get_cb, const Sp<Query>& q, Value::Filter&& filter, const OnListen& onListen)
 {
     // find exact match
     auto op = getOp(q);
@@ -192,7 +192,7 @@ SearchCache::listen(const ValueCallback& get_cb, const Sp<Query>& q, const Value
     auto token = nextToken_++;
     if (nextToken_ == 0)
         nextToken_++;
-    return op->second->addListener(token, get_cb, q, filter) ? token : 0;
+    return op->second->addListener(token, get_cb, q, std::move(filter)) ? token : 0;
 }
 
 bool
diff --git a/src/op_cache.h b/src/op_cache.h
index a32751f..ba7c129 100644
--- a/src/op_cache.h
+++ b/src/op_cache.h
@@ -107,12 +107,12 @@ public:
     void onValuesAdded(const std::vector<Sp<Value>>& vals);
     void onValuesExpired(const std::vector<Sp<Value>>& vals);
 
-    bool addListener(size_t token, const ValueCallback& cb, const Sp<Query>& q, const Value::Filter& filter) {
+    bool addListener(size_t token, const ValueCallback& cb, const Sp<Query>& q, Value::Filter&& filter) {
         auto cached = cache.get(filter);
         if (not cached.empty() and not cb(cached, false)) {
             return false;
         }
-        listeners.emplace(token, LocalListener{q, filter, cb});
+        listeners.emplace(token, LocalListener{q, std::move(filter), cb});
         return true;
     }
 
@@ -167,7 +167,7 @@ public:
     SearchCache(SearchCache&&) = default;
 
     using OnListen = std::function<size_t(Sp<Query>, ValueCallback, SyncCallback)>;
-    size_t listen(const ValueCallback& get_cb, const Sp<Query>& q, const Value::Filter& filter, const OnListen& onListen);
+    size_t listen(const ValueCallback& get_cb, const Sp<Query>& q, Value::Filter&& filter, const OnListen& onListen);
 
     bool cancelListen(size_t gtoken, const time_point& now);
     void cancelAll(const std::function<void(size_t)>& onCancel);
diff --git a/src/parsed_message.h b/src/parsed_message.h
index 6f5063c..a144102 100644
--- a/src/parsed_message.h
+++ b/src/parsed_message.h
@@ -152,9 +152,10 @@ bool
 ParsedMessage::complete()
 {
     for (auto& e : value_parts) {
-        //std::cout << "part " << e.first << ": " << e.second.second.size() << "/" << e.second.first << std::endl;
-        if (e.second.first > e.second.second.size())
+        if (e.second.first > e.second.second.size()) {
+            //std::cout << "uncomplete part " << e.first << ": " << e.second.second.size() << "/" << e.second.first << std::endl;
             return false;
+        }
     }
     for (auto& e : value_parts) {
         msgpack::unpacked msg;
diff --git a/src/search.h b/src/search.h
index 5ec9160..717bf8a 100644
--- a/src/search.h
+++ b/src/search.h
@@ -547,9 +547,9 @@ struct Dht::Search {
         }
     }
 
-    size_t listen(const ValueCallback& cb, const Value::Filter& f, const Sp<Query>& q, Scheduler& scheduler) {
+    size_t listen(const ValueCallback& cb, Value::Filter&& f, const Sp<Query>& q, Scheduler& scheduler) {
         //DHT_LOG.e(id, "[search %s IPv%c] listen", id.toString().c_str(), (af == AF_INET) ? '4' : '6');
-        return cache.listen(cb, q, f, [&](const Sp<Query>& q, ValueCallback vcb, SyncCallback scb){
+        return cache.listen(cb, q, std::move(f), [&](const Sp<Query>& q, ValueCallback vcb, SyncCallback scb){
             done = false;
             auto token = ++listener_token;
             listeners.emplace(token, SearchListener{q, vcb, scb});
diff --git a/src/securedht.cpp b/src/securedht.cpp
index 55a7bd8..ae1dc0f 100644
--- a/src/securedht.cpp
+++ b/src/securedht.cpp
@@ -121,7 +121,7 @@ Sp<crypto::PublicKey>
 SecureDht::getPublicKey(const InfoHash& node) const
 {
     if (node == getId())
-        return std::make_shared<crypto::PublicKey>(certificate_->getPublicKey());
+        return certificate_->getSharedPublicKey();
     auto it = nodesPubKeys_.find(node);
     if (it == nodesPubKeys_.end())
         return nullptr;
@@ -217,7 +217,7 @@ SecureDht::findPublicKey(const InfoHash& node, const std::function<void(const Sp
     }
     findCertificate(node, [=](const Sp<crypto::Certificate>& crt) {
         if (crt && *crt) {
-            auto pk = std::make_shared<crypto::PublicKey>(crt->getPublicKey());
+            auto pk = crt->getSharedPublicKey();
             if (*pk) {
                 nodesPubKeys_[pk->getId()] = pk;
                 if (cb) cb(pk);
diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp
index 31b7b3c..5eb843d 100644
--- a/src/thread_pool.cpp
+++ b/src/thread_pool.cpp
@@ -26,7 +26,7 @@
 
 namespace dht {
 
-constexpr const size_t IO_THREADS_MAX {64};
+constexpr const size_t IO_THREADS_MAX {512};
 
 ThreadPool&
 ThreadPool::computation()
@@ -65,33 +65,39 @@ ThreadPool::run(std::function<void()>&& cb)
 
     // launch new thread if necessary
     if (not readyThreads_ && threads_.size() < maxThreads_) {
-        threads_.emplace_back(std::make_unique<std::thread>([this]() {
-            while (true) {
-                std::function<void()> task;
-
-                // pick task from queue
-                {
-                    std::unique_lock<std::mutex> l(lock_);
-                    readyThreads_++;
-                    cv_.wait(l, [&](){
-                        return not running_ or not tasks_.empty();
-                    });
-                    readyThreads_--;
-                    if (not running_)
-                        break;
-                    task = std::move(tasks_.front());
-                    tasks_.pop();
-                }
-
-                // run task
-                try {
-                    task();
-                } catch (const std::exception& e) {
-                    // LOG_ERR("Exception running task: %s", e.what());
-                    std::cerr << "Exception running task: " << e.what() << std::endl;
+        try {
+            threads_.emplace_back(std::make_unique<std::thread>([this]() {
+                while (true) {
+                    std::function<void()> task;
+
+                    // pick task from queue
+                    {
+                        std::unique_lock<std::mutex> l(lock_);
+                        readyThreads_++;
+                        cv_.wait(l, [&](){
+                            return not running_ or not tasks_.empty();
+                        });
+                        readyThreads_--;
+                        if (not running_)
+                            break;
+                        task = std::move(tasks_.front());
+                        tasks_.pop();
+                    }
+
+                    // run task
+                    try {
+                        task();
+                    } catch (const std::exception& e) {
+                        // LOG_ERR("Exception running task: %s", e.what());
+                        std::cerr << "Exception running task: " << e.what() << std::endl;
+                    }
                 }
-            }
-        }));
+            }));
+        } catch(const std::exception& e) {
+            std::cerr << "Exception starting thread: " << e.what() << std::endl;
+            if (threads_.empty())
+                throw;
+        }
     }
 
     // push task to queue
diff --git a/src/utils.cpp b/src/utils.cpp
index f29a9dd..046830f 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -35,12 +35,14 @@
 
 namespace dht {
 
-static constexpr std::array<uint8_t, 12> MAPPED_IPV4_PREFIX {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}};
-
 const char* version() {
     return PACKAGE_VERSION;
 }
 
+const HexMap hex_map = {};
+
+static constexpr std::array<uint8_t, 12> MAPPED_IPV4_PREFIX {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}};
+
 std::pair<std::string, std::string>
 splitPort(const std::string& s) {
     if (s.empty())
@@ -101,21 +103,26 @@ SockAddr::setAddress(const char* address)
         throw std::runtime_error(std::string("Can't parse IP address: ") + strerror(errno));
 }
 
-std::string
-print_addr(const sockaddr* sa, socklen_t slen)
+void print_addr(std::ostream& out, const sockaddr* sa, socklen_t slen)
 {
     char hbuf[NI_MAXHOST];
     char sbuf[NI_MAXSERV];
-    std::ostringstream out;
     if (sa and slen and !getnameinfo(sa, slen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV)) {
         if (sa->sa_family == AF_INET6)
-            out << "[" << hbuf << "]";
+            out << '[' << hbuf << ']';
         else
             out << hbuf;
         if (std::strcmp(sbuf, "0"))
-            out << ":" << sbuf;
+            out << ':' << sbuf;
     } else
         out << "[invalid address]";
+}
+
+std::string
+print_addr(const sockaddr* sa, socklen_t slen)
+{
+    std::ostringstream out;
+    print_addr(out, sa, slen);
     return out.str();
 }
 
diff --git a/src/value.cpp b/src/value.cpp
index 9529a47..d433978 100644
--- a/src/value.cpp
+++ b/src/value.cpp
@@ -98,7 +98,7 @@ ValueType::DEFAULT_STORE_POLICY(InfoHash, const std::shared_ptr<Value>& v, const
 size_t
 Value::size() const
 {
-    return cypher.size() + data.size() + signature.size()  + user_type.size();
+    return cypher.size() + data.size() + signature.size() + user_type.size();
 }
 
 void
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index b56507d..29de73d 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -1,9 +1,4 @@
-if (OPENDHT_SHARED)
-    set (OPENDHT_LIBS opendht)
-    set (OPENDHT_C_LIBS opendht-c)
-else ()
-    set (OPENDHT_LIBS opendht-static)
-    set (OPENDHT_C_LIBS opendht-c-static)
+if (OPENDHT_STATIC)
     if (MSVC)
         set (MSC_COMPAT_SOURCES ${MSC_COMPAT_DIR}/wingetopt.c)
     endif ()
@@ -11,8 +6,8 @@ endif ()
 
 function (configure_tool name extra_files)
     add_executable (${name} ${name}.cpp ${extra_files})
-    add_dependencies(${name} ${OPENDHT_LIBS})
-    target_link_libraries (${name} LINK_PUBLIC ${OPENDHT_LIBS} ${READLINE_LIBRARIES})
+    add_dependencies(${name} opendht)
+    target_link_libraries (${name} LINK_PUBLIC opendht ${READLINE_LIBRARIES})
     if (MSVC)
         target_sources(${name} PRIVATE ${MSC_COMPAT_SOURCES})
         target_include_directories (${name} PRIVATE ${MSC_COMPAT_DIR})
@@ -31,8 +26,8 @@ endif ()
 
 if (OPENDHT_C)
     add_executable (dhtcnode dhtcnode.c)
-    add_dependencies(dhtcnode ${OPENDHT_C_LIBS})
-    target_link_libraries (dhtcnode LINK_PUBLIC ${OPENDHT_C_LIBS} ${READLINE_LIBRARIES})
+    add_dependencies(dhtcnode opendht-c)
+    target_link_libraries (dhtcnode LINK_PUBLIC opendht-c ${READLINE_LIBRARIES})
     target_include_directories (dhtcnode SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/c)
 endif ()
 
@@ -43,7 +38,7 @@ endif ()
 install (TARGETS dhtnode dhtscanner dhtchat RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
 
 if (OPENDHT_SYSTEMD)
-    if (NOT DEFINED OPENDHT_SYSTEMD_UNIT_FILE_LOCATION)
+    if (NOT DEFINED OPENDHT_SYSTEMD_UNIT_FILE_LOCATION OR NOT OPENDHT_SYSTEMD_UNIT_FILE_LOCATION)
         execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} systemd --variable=systemdsystemunitdir
                         OUTPUT_VARIABLE SYSTEMD_UNIT_INSTALL_DIR)
         message("-- Using Systemd unit installation directory by pkg-config: " ${SYSTEMD_UNIT_INSTALL_DIR})
@@ -51,23 +46,30 @@ if (OPENDHT_SYSTEMD)
         message("-- Using Systemd unit installation directory requested: " ${OPENDHT_SYSTEMD_UNIT_FILE_LOCATION})
         set(SYSTEMD_UNIT_INSTALL_DIR ${OPENDHT_SYSTEMD_UNIT_FILE_LOCATION})
     endif()
-    string(REGEX REPLACE "[ \t\n]+" "" SYSTEMD_UNIT_INSTALL_DIR "${SYSTEMD_UNIT_INSTALL_DIR}")
-    set (systemdunitdir "${SYSTEMD_UNIT_INSTALL_DIR}")
 
     configure_file (
         systemd/dhtnode.service.in
         systemd/dhtnode.service
         @ONLY
     )
-    install (FILES ${CMAKE_CURRENT_BINARY_DIR}/systemd/dhtnode.service DESTINATION ${systemdunitdir})
-    install (FILES systemd/dhtnode.conf DESTINATION ${sysconfdir})
+    if (SYSTEMD_UNIT_INSTALL_DIR)
+        string(REGEX REPLACE "[ \t\n]+" "" SYSTEMD_UNIT_INSTALL_DIR "${SYSTEMD_UNIT_INSTALL_DIR}")
+        set (systemdunitdir "${SYSTEMD_UNIT_INSTALL_DIR}")
+        install (FILES ${CMAKE_CURRENT_BINARY_DIR}/systemd/dhtnode.service DESTINATION ${systemdunitdir})
+        install (FILES systemd/dhtnode.conf DESTINATION ${sysconfdir})
+    else()
+        message(WARNING "Systemd unit installation directory not found. The systemd unit won't be installed.")
+    endif()
+
     if (OPENDHT_PYTHON)
         configure_file (
             systemd/dhtcluster.service.in
             systemd/dhtcluster.service
             @ONLY
         )
-        install (FILES ${CMAKE_CURRENT_BINARY_DIR}/systemd/dhtcluster.service DESTINATION ${systemdunitdir})
-        install (FILES systemd/dhtcluster.conf DESTINATION ${sysconfdir})
+        if (SYSTEMD_UNIT_INSTALL_DIR)
+            install (FILES ${CMAKE_CURRENT_BINARY_DIR}/systemd/dhtcluster.service DESTINATION ${systemdunitdir})
+            install (FILES systemd/dhtcluster.conf DESTINATION ${sysconfdir})
+        endif()
     endif()
 endif ()
diff --git a/tools/systemd/dhtcluster.conf b/tools/systemd/dhtcluster.conf
index 7311d6d..f0b8fb7 100644
--- a/tools/systemd/dhtcluster.conf
+++ b/tools/systemd/dhtcluster.conf
@@ -1 +1 @@
-DHT_ARGS=-b bootstrap.ring.cx -p 4222 -n 16
\ No newline at end of file
+DHT_ARGS=-b bootstrap.jami.net -p 4222 -n 16
\ No newline at end of file
diff --git a/tools/tools_common.h b/tools/tools_common.h
index cfeade0..fe1ca55 100644
--- a/tools/tools_common.h
+++ b/tools/tools_common.h
@@ -187,8 +187,8 @@ getDhtConfig(dht_params& params)
             context.logger = dht::log::getStdLogger();
     }
     if (context.logger) {
-        context.statusChangedCallback = [logger = *context.logger](dht::NodeStatus status4, dht::NodeStatus status6) {
-            logger.WARN("Connectivity changed: IPv4: %s, IPv6: %s", dht::statusToStr(status4), dht::statusToStr(status6));
+        context.statusChangedCallback = [logger = context.logger](dht::NodeStatus status4, dht::NodeStatus status6) {
+            logger->w("Connectivity changed: IPv4: %s, IPv6: %s", dht::statusToStr(status4), dht::statusToStr(status6));
         };
     }
     return {std::move(config), std::move(context)};

More details

Full run details

Historical runs